├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── build.xml ├── gradle-mvn-push.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── android-support-annotations.jar ├── android-support-v4.jar └── android.jar ├── pom-child.xml ├── pom.xml ├── src └── com │ └── activeandroid │ ├── ActiveAndroid.java │ ├── Cache.java │ ├── Configuration.java │ ├── DatabaseHelper.java │ ├── Interface.java │ ├── Model.java │ ├── ModelInfo.java │ ├── TableInfo.java │ ├── annotation │ ├── Column.java │ └── Table.java │ ├── app │ └── Application.java │ ├── content │ └── ContentProvider.java │ ├── query │ ├── Delete.java │ ├── From.java │ ├── Join.java │ ├── Select.java │ ├── Set.java │ ├── Sqlable.java │ └── Update.java │ ├── rx │ └── RxSelect.java │ ├── rxschedulers │ ├── AndroidSchedulers.java │ ├── HandlerScheduler.java │ ├── RxAndroidPlugins.java │ └── RxAndroidSchedulersHook.java │ ├── serializer │ ├── BigDecimalSerializer.java │ ├── CalendarSerializer.java │ ├── FileSerializer.java │ ├── SqlDateSerializer.java │ ├── TypeSerializer.java │ ├── UUIDSerializer.java │ └── UtilDateSerializer.java │ ├── sqlbrite │ ├── BackpressureBufferLastOperator.java │ ├── BriteContentResolver.java │ ├── BriteDatabase.java │ ├── QueryObservable.java │ ├── QueryToListOperator.java │ ├── QueryToOneOperator.java │ └── SqlBrite.java │ ├── util │ ├── IOUtils.java │ ├── Log.java │ ├── NaturalOrderComparator.java │ ├── ReflectionUtils.java │ ├── SQLiteUtils.java │ ├── SqlParser.java │ └── Tokenizer.java │ └── widget │ └── ModelAdapter.java ├── tests ├── .gitignore ├── AndroidManifest.xml ├── assets │ └── migrations │ │ └── 2.sql ├── pom.xml ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-ldpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── raw │ │ ├── block_comment.sql │ │ ├── block_comment_with_semicolon.sql │ │ ├── block_comment_with_string.sql │ │ ├── block_with_line_comment.sql │ │ ├── complex.sql │ │ ├── invalid_block_comment.sql │ │ ├── line_comment.sql │ │ ├── line_comment_and_block_end.sql │ │ ├── line_comment_with_semicolon.sql │ │ ├── line_comment_with_string.sql │ │ ├── string_with_block_comment.sql │ │ ├── string_with_line_comment.sql │ │ ├── string_with_semicolon.sql │ │ ├── string_with_whitespace.sql │ │ ├── two_statements.sql │ │ └── whitespace.sql │ └── values │ │ └── strings.xml └── src │ └── com │ └── activeandroid │ └── test │ ├── ActiveAndroidTestCase.java │ ├── CacheTest.java │ ├── ConfigurationTest.java │ ├── MockModel.java │ ├── ModelTest.java │ ├── parser │ ├── ParserConfigurationTest.java │ └── ParserTest.java │ └── query │ ├── CountTest.java │ ├── DeleteTest.java │ ├── ExistsTest.java │ ├── FromTest.java │ ├── SelectTest.java │ ├── SqlableTestCase.java │ └── UpdateTest.java └── wait_for_emulator /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | lint.xml 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | 12 | #IntelliJ IDEA 13 | .idea 14 | *.iml 15 | *.ipr 16 | *.iws 17 | classes 18 | gen-external-apklibs 19 | 20 | #Maven 21 | target 22 | release.properties 23 | pom.xml.* 24 | 25 | #Ant 26 | ant.properties 27 | local.properties 28 | proguard.cfg 29 | proguard-project.txt 30 | 31 | #Other 32 | .DS_Store 33 | dist/ 34 | tmp 35 | 36 | # Gradle 37 | .gradle 38 | /build 39 | /out 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk7 3 | env: ANDROID_SDK=android-16 ANDROID_ABI=armeabi-v7a 4 | 5 | before_install: 6 | # Install ia32-libs (necessary for Android SDK to run on 64-bit linux) 7 | # - sudo apt-get clean && sudo apt-get update 8 | - sudo apt-get update -qq 9 | - sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch 10 | 11 | # Install Android SDK 12 | - wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz 13 | - tar -zxf android-sdk_r22.6.2-linux.tgz 14 | - ls 15 | - export ANDROID_HOME=`pwd`/android-sdk-linux 16 | - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools 17 | 18 | # Install build-tools 19 | - wget https://dl-ssl.google.com/android/repository/build-tools_r19.0.3-linux.zip 20 | - unzip build-tools_r19.0.3-linux.zip -d $ANDROID_HOME 21 | - mkdir -p $ANDROID_HOME/build-tools/ 22 | - mv $ANDROID_HOME/android-4.4.2 $ANDROID_HOME/build-tools/19.0.1 23 | 24 | # Install required Android components 25 | - android list sdk --extended 26 | # Do you accept the license 'android-sdk-license-bcbbd656' [y/n]: 27 | - echo -ne "y\n" | android update sdk --filter system-image,platform-tools,extra-android-support,$ANDROID_SDK --no-ui --force 28 | 29 | # Create and start emulator 30 | - echo no | android create avd --force -n test -t $ANDROID_SDK --abi $ANDROID_ABI 31 | - android list avds 32 | - emulator -avd test -no-skin -no-audio -no-window & 33 | 34 | before_script: 35 | # Make sure the emulator has started before running tests 36 | - chmod +x ./wait_for_emulator 37 | - ./wait_for_emulator 38 | 39 | script: 40 | - mvn clean install -e 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/pardom/ActiveAndroid.png?branch=master)](https://travis-ci.org/pardom/ActiveAndroid) [![Stories in Ready](https://badge.waffle.io/pardom/ActiveAndroid.png)](http://waffle.io/pardom/ActiveAndroid) 2 | # ActiveAndroidRx 3 | 4 | Wrapper around ActiveAndroid which introduces reactive queries with SQLBrite from Square (https://github.com/square/sqlbrite). 5 | You can use it as alternative for Android loaders! 6 | 7 | ## Usage 8 | 9 | RxSelect.from(MyEntity.class).where(...).execute().subscribe(myEntitiesList -> ...); 10 | 11 | ## Download 12 | 13 | Grab via Gradle: 14 | ```groovy 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | compile 'com.github.vicpinm:activeandroidrx:3.1.5' 20 | ``` 21 | 22 | ## Documentation 23 | 24 | * [Getting started](http://github.com/pardom/ActiveAndroid/wiki/Getting-started) 25 | * [Creating your database model](http://github.com/pardom/ActiveAndroid/wiki/Creating-your-database-model) 26 | * [Saving to the database](http://github.com/pardom/ActiveAndroid/wiki/Saving-to-the-database) 27 | * [Querying the database](http://github.com/pardom/ActiveAndroid/wiki/Querying-the-database) 28 | * [Type serializers](http://github.com/pardom/ActiveAndroid/wiki/Type-serializers) 29 | * [Using the content provider](http://github.com/pardom/ActiveAndroid/wiki/Using-the-content-provider) 30 | * [Schema migrations](http://github.com/pardom/ActiveAndroid/wiki/Schema-migrations) 31 | * [Pre-populated-databases](http://github.com/pardom/ActiveAndroid/wiki/Pre-populated-databases) 32 | * [Running the Test Suite](https://github.com/pardom/ActiveAndroid/wiki/Running-the-Test-Suite) 33 | 34 | ## License 35 | 36 | [Apache Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 37 | 38 | Copyright (C) 2010 Michael Pardo 39 | 40 | Licensed under the Apache License, Version 2.0 (the "License"); 41 | you may not use this file except in compliance with the License. 42 | You may obtain a copy of the License at 43 | 44 | http://www.apache.org/licenses/LICENSE-2.0 45 | 46 | Unless required by applicable law or agreed to in writing, software 47 | distributed under the License is distributed on an "AS IS" BASIS, 48 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 49 | See the License for the specific language governing permissions and 50 | limitations under the License. 51 | 52 | ## Contributing 53 | 54 | Please fork this repository and contribute back using [pull requests](http://github.com/pardom/ActiveAndroid/pulls). 55 | 56 | Any contributions, large or small, major features, bug fixes, unit tests are welcomed and appreciated but will be thoroughly reviewed and discussed. 57 | 58 | You can run the test suite by following the instructions on the [Running the Test Suite](https://github.com/pardom/ActiveAndroid/wiki/Running-the-Test-Suite) Wiki page. 59 | 60 | 61 | ## Author 62 | 63 | Víctor Manuel Pineda Murcia | http://vicpinm.github.io/ActiveAndroidRx/ 64 | 65 | Original Project: 66 | Michael Pardo | www.michaelpardo.com | www.activeandroid.com 67 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply from: 'gradle-mvn-push.gradle' 3 | 4 | group = 'com.github.vicpinm' 5 | version = '3.1.5' 6 | 7 | sourceCompatibility = 1.7 8 | targetCompatibility = 1.7 9 | 10 | sourceSets { 11 | main { 12 | java { 13 | srcDir 'src' 14 | } 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: '*.jar') 24 | compile 'com.google.code.gson:gson:2.3.1' 25 | compile 'io.reactivex:rxjava:1.0.14' 26 | 27 | } 28 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * Copyright 2014 Michael Pardo 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | apply plugin: 'maven' 19 | apply plugin: 'signing' 20 | 21 | def isReleaseBuild() { 22 | return VERSION_NAME.contains("SNAPSHOT") == false 23 | } 24 | 25 | def getReleaseRepositoryUrl() { 26 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 27 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 28 | } 29 | 30 | def getSnapshotRepositoryUrl() { 31 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 32 | : "https://oss.sonatype.org/content/repositories/snapshots/" 33 | } 34 | 35 | def getRepositoryUsername() { 36 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 37 | } 38 | 39 | def getRepositoryPassword() { 40 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 41 | } 42 | 43 | afterEvaluate { project -> 44 | uploadArchives { 45 | repositories { 46 | mavenDeployer { 47 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 48 | 49 | pom.groupId = GROUP 50 | pom.artifactId = POM_ARTIFACT_ID 51 | pom.version = VERSION_NAME 52 | 53 | repository(url: getReleaseRepositoryUrl()) { 54 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 55 | } 56 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 57 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 58 | } 59 | 60 | pom.project { 61 | name POM_NAME 62 | packaging POM_PACKAGING 63 | description POM_DESCRIPTION 64 | url POM_URL 65 | 66 | scm { 67 | url POM_SCM_URL 68 | connection POM_SCM_CONNECTION 69 | developerConnection POM_SCM_DEV_CONNECTION 70 | } 71 | 72 | licenses { 73 | license { 74 | name POM_LICENCE_NAME 75 | url POM_LICENCE_URL 76 | distribution POM_LICENCE_DIST 77 | } 78 | } 79 | 80 | developers { 81 | developer { 82 | id POM_DEVELOPER_ID 83 | name POM_DEVELOPER_NAME 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | signing { 92 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 93 | sign configurations.archives 94 | } 95 | 96 | task javadocs(type: Javadoc) { 97 | source = sourceSets.main.allJava 98 | classpath = configurations.compile 99 | } 100 | 101 | task javadocsJar(type: Jar, dependsOn: javadocs) { 102 | classifier = 'javadoc' 103 | from javadocs.destinationDir 104 | } 105 | 106 | task sourcesJar(type: Jar) { 107 | classifier = 'sources' 108 | from sourceSets.main.allJava 109 | } 110 | 111 | artifacts { 112 | archives sourcesJar 113 | archives javadocsJar 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=3.1.5 2 | VERSION_CODE=2 3 | GROUP=com.github.vicpinm 4 | 5 | POM_DESCRIPTION=ActiveAndroid fork with reactive extensions. 6 | POM_URL=https://github.com/vicpinm/ActiveAndroidRx 7 | POM_SCM_URL=https://github.com/vicpinm/ActiveAndroidRx 8 | POM_SCM_CONNECTION=scm:git@github.com:vicpinm/ActiveAndroidRx.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:vicpinm/ActiveAndroidRx.git 10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 12 | POM_LICENCE_DIST=repo 13 | POM_DEVELOPER_ID=vicipnm 14 | POM_DEVELOPER_NAME=Victor Manuel Pineda Murcia 15 | 16 | POM_NAME=ActiveAndroidRx 17 | POM_ARTIFACT_ID=activeandroidrx 18 | POM_PACKAGING=jar 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Nov 29 09:06:20 EST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /libs/android-support-annotations.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/libs/android-support-annotations.jar -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/libs/android-support-v4.jar -------------------------------------------------------------------------------- /libs/android.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/libs/android.jar -------------------------------------------------------------------------------- /pom-child.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 4.0.0 8 | com.activeandroidrx 9 | activeandroidrx 10 | 3.1.1 11 | jar 12 | ActiveAndroidRx 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | repo 19 | 20 | 21 | 22 | 23 | com.activeandroidrx 24 | activeandroidrx-parent 25 | 3.1.1 26 | ./pom.xml 27 | 28 | 29 | 30 | scm:git:git@github.com:vicpinm/ActiveAndroidRx.git 31 | scm:git:git@github.com:vicpinm/ActiveAndroidRx.git 32 | git@github.com:vicpinm/ActiveAndroidRx.git 33 | 34 | 35 | 36 | 1.6 37 | 16 38 | 4.1.1.4 39 | r7 40 | 41 | 42 | 43 | 44 | com.google.android 45 | android 46 | ${platform.version} 47 | provided 48 | 49 | 50 | com.google.android 51 | android-test 52 | ${platform.version} 53 | provided 54 | 55 | 56 | com.google.android 57 | support-v4 58 | ${platform.support-version} 59 | 60 | 61 | 62 | 63 | src 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-surefire-plugin 68 | 2.14.1 69 | 70 | 71 | maven-compiler-plugin 72 | 3.1 73 | 74 | ${java.version} 75 | ${java.version} 76 | 77 | 78 | 79 | com.jayway.maven.plugins.android.generation2 80 | android-maven-plugin 81 | 3.8.2 82 | 83 | 84 | ${env.ANDROID_HOME} 85 | ${platform.sdk} 86 | 87 | 88 | true 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.github.vicpinm 6 | activeandroidrx-parent 7 | 3.1.1 8 | pom 9 | ActiveAndroidRx - Parent 10 | 11 | 12 | pom-child.xml 13 | tests 14 | 15 | 16 | 17 | 1.6 18 | 16 19 | 4.1.1.4 20 | r7 21 | 22 | 23 | 24 | 25 | 26 | 27 | com.jayway.maven.plugins.android.generation2 28 | android-maven-plugin 29 | 3.8.2 30 | 31 | 32 | maven-compiler-plugin 33 | 3.1 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/com/activeandroid/ActiveAndroid.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.content.Context; 20 | import android.database.Cursor; 21 | 22 | import com.activeandroid.rxschedulers.AndroidSchedulers; 23 | import com.activeandroid.sqlbrite.BriteDatabase; 24 | import com.activeandroid.sqlbrite.SqlBrite; 25 | import com.activeandroid.util.Log; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | import rx.Observable; 31 | import rx.functions.Func1; 32 | import rx.schedulers.Schedulers; 33 | 34 | public final class ActiveAndroid { 35 | ////////////////////////////////////////////////////////////////////////////////////// 36 | // PUBLIC METHODS 37 | ////////////////////////////////////////////////////////////////////////////////////// 38 | 39 | public static void initialize(Context context) { 40 | initialize(new Configuration.Builder(context).create()); 41 | } 42 | 43 | public static void initialize(Configuration configuration) { 44 | initialize(configuration, false); 45 | } 46 | 47 | public static void initialize(Context context, boolean loggingEnabled) { 48 | initialize(new Configuration.Builder(context).create(), loggingEnabled); 49 | } 50 | 51 | public static void initialize(Configuration configuration, boolean loggingEnabled) { 52 | // Set logging enabled first 53 | setLoggingEnabled(loggingEnabled); 54 | Cache.initialize(configuration); 55 | } 56 | 57 | public static void clearCache() { 58 | Cache.clear(); 59 | } 60 | 61 | public static void dispose() { 62 | Cache.dispose(); 63 | } 64 | 65 | public static void setLoggingEnabled(boolean enabled) { 66 | Log.setEnabled(enabled); 67 | } 68 | 69 | public static BriteDatabase getDatabase() { 70 | return Cache.openDatabase(); 71 | } 72 | 73 | public static BriteDatabase.Transaction beginTransaction() { 74 | return Cache.openDatabase().newTransaction(); 75 | } 76 | 77 | public static void endTransaction(BriteDatabase.Transaction transaction) { 78 | transaction.end(); 79 | } 80 | 81 | public static void setTransactionSuccessful(BriteDatabase.Transaction transaction) { 82 | transaction.markSuccessful(); 83 | } 84 | 85 | 86 | public static void execSQL(String sql) { 87 | Cache.openDatabase().execute(sql); 88 | } 89 | 90 | public static void execSQL(String sql, Object[] bindArgs) { 91 | Cache.openDatabase().execute(sql, bindArgs); 92 | } 93 | 94 | public static Observable query(String table, String sql) { 95 | return Cache.openDatabase().createQuery(table, sql); 96 | } 97 | 98 | public static Observable queryCursor(Class clase, String sql) { 99 | 100 | TableInfo tableInfo = new TableInfo(clase); 101 | return Cache.openDatabase().createQuery(tableInfo.getTableName(), sql) 102 | .subscribeOn(Schedulers.io()) 103 | .map(new Func1() { 104 | @Override 105 | public Cursor call(SqlBrite.Query query) { 106 | return query.run(); 107 | } 108 | }) 109 | .observeOn(AndroidSchedulers.mainThread()); 110 | } 111 | 112 | public static Observable> queryObject(final Class clase,String sql) { 113 | 114 | TableInfo tableInfo = new TableInfo(clase); 115 | 116 | return Cache.openDatabase().createQuery(tableInfo.getTableName(), sql) 117 | .subscribeOn(Schedulers.io()) 118 | .map(new Func1>() { 119 | @Override 120 | public List call(SqlBrite.Query query) { 121 | try { 122 | Cursor cursor = query.run(); 123 | List data = new ArrayList(); 124 | if (cursor != null && cursor.getCount() > 0) { 125 | while (cursor.moveToNext()) { 126 | Model model = clase.newInstance(); 127 | model.loadFromCursor(cursor); 128 | data.add(model); 129 | } 130 | } 131 | 132 | return data; 133 | } catch(Exception ex){ 134 | ex.printStackTrace(); 135 | } 136 | 137 | return null; 138 | 139 | } 140 | }) 141 | .observeOn(AndroidSchedulers.mainThread()); 142 | } 143 | 144 | 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/com/activeandroid/Cache.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.content.Context; 20 | import android.support.v4.util.LruCache; 21 | 22 | import com.activeandroid.serializer.TypeSerializer; 23 | import com.activeandroid.sqlbrite.BriteDatabase; 24 | import com.activeandroid.sqlbrite.SqlBrite; 25 | import com.activeandroid.util.Log; 26 | 27 | import java.util.Collection; 28 | 29 | public final class Cache { 30 | ////////////////////////////////////////////////////////////////////////////////////// 31 | // PUBLIC CONSTANTS 32 | ////////////////////////////////////////////////////////////////////////////////////// 33 | 34 | public static final int DEFAULT_CACHE_SIZE = 1024; 35 | 36 | ////////////////////////////////////////////////////////////////////////////////////// 37 | // PRIVATE MEMBERS 38 | ////////////////////////////////////////////////////////////////////////////////////// 39 | 40 | private static Context sContext; 41 | 42 | private static ModelInfo sModelInfo; 43 | private static DatabaseHelper sDatabaseHelper; 44 | 45 | private static LruCache sEntities; 46 | 47 | private static SqlBrite sSqlBrite; 48 | 49 | private static BriteDatabase sDatabase; 50 | 51 | private static boolean sIsInitialized = false; 52 | 53 | ////////////////////////////////////////////////////////////////////////////////////// 54 | // CONSTRUCTORS 55 | ////////////////////////////////////////////////////////////////////////////////////// 56 | 57 | private Cache() { 58 | } 59 | 60 | ////////////////////////////////////////////////////////////////////////////////////// 61 | // PUBLIC METHODS 62 | ////////////////////////////////////////////////////////////////////////////////////// 63 | 64 | public static synchronized void initialize(Configuration configuration) { 65 | if (sIsInitialized) { 66 | Log.v("ActiveAndroid already initialized."); 67 | return; 68 | } 69 | 70 | sContext = configuration.getContext(); 71 | sModelInfo = new ModelInfo(configuration); 72 | sDatabaseHelper = new DatabaseHelper(configuration); 73 | sSqlBrite = SqlBrite.create(); 74 | sDatabase = sSqlBrite.wrapDatabaseHelper(sDatabaseHelper); 75 | 76 | 77 | // TODO: It would be nice to override sizeOf here and calculate the memory 78 | // actually used, however at this point it seems like the reflection 79 | // required would be too costly to be of any benefit. We'll just set a max 80 | // object size instead. 81 | sEntities = new LruCache(configuration.getCacheSize()); 82 | 83 | openDatabase(); 84 | 85 | sIsInitialized = true; 86 | 87 | Log.v("ActiveAndroid initialized successfully."); 88 | } 89 | 90 | public static synchronized void clear() { 91 | sEntities.evictAll(); 92 | Log.v("Cache cleared."); 93 | } 94 | 95 | public static synchronized void dispose() { 96 | closeDatabase(); 97 | 98 | sEntities = null; 99 | sModelInfo = null; 100 | sDatabaseHelper = null; 101 | 102 | sIsInitialized = false; 103 | 104 | Log.v("ActiveAndroid disposed. Call initialize to use library."); 105 | } 106 | 107 | // Database access 108 | 109 | public static boolean isInitialized() { 110 | return sIsInitialized; 111 | } 112 | 113 | public static synchronized BriteDatabase openDatabase() { 114 | return sDatabase; 115 | } 116 | 117 | public static synchronized void closeDatabase() { 118 | sDatabaseHelper.close(); 119 | } 120 | 121 | // Context access 122 | 123 | public static Context getContext() { 124 | return sContext; 125 | } 126 | 127 | // Entity cache 128 | 129 | public static String getIdentifier(Class type, Long id) { 130 | return getTableName(type) + "@" + id; 131 | } 132 | 133 | public static String getIdentifier(Model entity) { 134 | return getIdentifier(entity.getClass(), entity.getId()); 135 | } 136 | 137 | public static synchronized void addEntity(Model entity) { 138 | sEntities.put(getIdentifier(entity), entity); 139 | } 140 | 141 | public static synchronized Model getEntity(Class type, long id) { 142 | return sEntities.get(getIdentifier(type, id)); 143 | } 144 | 145 | public static synchronized void removeEntity(Model entity) { 146 | sEntities.remove(getIdentifier(entity)); 147 | } 148 | 149 | // Model cache 150 | 151 | public static synchronized Collection getTableInfos() { 152 | return sModelInfo.getTableInfos(); 153 | } 154 | 155 | public static synchronized TableInfo getTableInfo(Class type) { 156 | return sModelInfo.getTableInfo(type); 157 | } 158 | 159 | public static synchronized TypeSerializer getParserForType(Class type) { 160 | return sModelInfo.getTypeSerializer(type); 161 | } 162 | 163 | public static synchronized String getTableName(Class type) { 164 | return sModelInfo.getTableInfo(type).getTableName(); 165 | } 166 | 167 | public static DatabaseHelper getHelper(){ 168 | return sDatabaseHelper; 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/com/activeandroid/DatabaseHelper.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.content.Context; 20 | import android.database.sqlite.SQLiteDatabase; 21 | import android.database.sqlite.SQLiteOpenHelper; 22 | import android.text.TextUtils; 23 | 24 | import com.activeandroid.util.IOUtils; 25 | import com.activeandroid.util.Log; 26 | import com.activeandroid.util.NaturalOrderComparator; 27 | import com.activeandroid.util.SQLiteUtils; 28 | import com.activeandroid.util.SqlParser; 29 | 30 | import java.io.BufferedReader; 31 | import java.io.File; 32 | import java.io.FileOutputStream; 33 | import java.io.IOException; 34 | import java.io.InputStream; 35 | import java.io.InputStreamReader; 36 | import java.io.OutputStream; 37 | import java.util.Arrays; 38 | import java.util.Collections; 39 | import java.util.List; 40 | 41 | public final class DatabaseHelper extends SQLiteOpenHelper { 42 | ////////////////////////////////////////////////////////////////////////////////////// 43 | // PUBLIC CONSTANTS 44 | ////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | public final static String MIGRATION_PATH = "migrations"; 47 | 48 | ////////////////////////////////////////////////////////////////////////////////////// 49 | // PRIVATE FIELDS 50 | ////////////////////////////////////////////////////////////////////////////////////// 51 | 52 | private final String mSqlParser; 53 | 54 | ////////////////////////////////////////////////////////////////////////////////////// 55 | // CONSTRUCTORS 56 | ////////////////////////////////////////////////////////////////////////////////////// 57 | 58 | public DatabaseHelper(Configuration configuration) { 59 | super(configuration.getContext(), configuration.getDatabaseName(), null, configuration.getDatabaseVersion()); 60 | copyAttachedDatabase(configuration.getContext(), configuration.getDatabaseName()); 61 | mSqlParser = configuration.getSqlParser(); 62 | } 63 | 64 | ////////////////////////////////////////////////////////////////////////////////////// 65 | // OVERRIDEN METHODS 66 | ////////////////////////////////////////////////////////////////////////////////////// 67 | 68 | @Override 69 | public void onOpen(SQLiteDatabase db) { 70 | executePragmas(db); 71 | }; 72 | 73 | @Override 74 | public void onCreate(SQLiteDatabase db) { 75 | executePragmas(db); 76 | executeCreate(db); 77 | executeMigrations(db, -1, db.getVersion()); 78 | executeCreateIndex(db); 79 | } 80 | 81 | @Override 82 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 83 | executePragmas(db); 84 | executeCreate(db); 85 | executeMigrations(db, oldVersion, newVersion); 86 | } 87 | 88 | ////////////////////////////////////////////////////////////////////////////////////// 89 | // PUBLIC METHODS 90 | ////////////////////////////////////////////////////////////////////////////////////// 91 | 92 | public void copyAttachedDatabase(Context context, String databaseName) { 93 | final File dbPath = context.getDatabasePath(databaseName); 94 | 95 | // If the database already exists, return 96 | if (dbPath.exists()) { 97 | return; 98 | } 99 | 100 | // Make sure we have a path to the file 101 | dbPath.getParentFile().mkdirs(); 102 | 103 | // Try to copy database file 104 | try { 105 | final InputStream inputStream = context.getAssets().open(databaseName); 106 | final OutputStream output = new FileOutputStream(dbPath); 107 | 108 | byte[] buffer = new byte[8192]; 109 | int length; 110 | 111 | while ((length = inputStream.read(buffer, 0, 8192)) > 0) { 112 | output.write(buffer, 0, length); 113 | } 114 | 115 | output.flush(); 116 | output.close(); 117 | inputStream.close(); 118 | } 119 | catch (IOException e) { 120 | Log.e("Failed to open file", e); 121 | } 122 | } 123 | 124 | ////////////////////////////////////////////////////////////////////////////////////// 125 | // PRIVATE METHODS 126 | ////////////////////////////////////////////////////////////////////////////////////// 127 | 128 | private void executePragmas(SQLiteDatabase db) { 129 | if (SQLiteUtils.FOREIGN_KEYS_SUPPORTED) { 130 | db.execSQL("PRAGMA foreign_keys=ON;"); 131 | Log.i("Foreign Keys supported. Enabling foreign key features."); 132 | } 133 | } 134 | 135 | private void executeCreateIndex(SQLiteDatabase db) { 136 | db.beginTransaction(); 137 | try { 138 | for (TableInfo tableInfo : Cache.getTableInfos()) { 139 | String[] definitions = SQLiteUtils.createIndexDefinition(tableInfo); 140 | 141 | for (String definition : definitions) { 142 | db.execSQL(definition); 143 | } 144 | } 145 | db.setTransactionSuccessful(); 146 | } 147 | finally { 148 | db.endTransaction(); 149 | } 150 | } 151 | 152 | private void executeCreate(SQLiteDatabase db) { 153 | db.beginTransaction(); 154 | try { 155 | for (TableInfo tableInfo : Cache.getTableInfos()) { 156 | db.execSQL(SQLiteUtils.createTableDefinition(tableInfo)); 157 | } 158 | db.setTransactionSuccessful(); 159 | } 160 | finally { 161 | db.endTransaction(); 162 | } 163 | } 164 | 165 | private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) { 166 | boolean migrationExecuted = false; 167 | try { 168 | final List files = Arrays.asList(Cache.getContext().getAssets().list(MIGRATION_PATH)); 169 | Collections.sort(files, new NaturalOrderComparator()); 170 | 171 | db.beginTransaction(); 172 | try { 173 | for (String file : files) { 174 | try { 175 | final int version = Integer.valueOf(file.replace(".sql", "")); 176 | 177 | if (version > oldVersion && version <= newVersion) { 178 | executeSqlScript(db, file); 179 | migrationExecuted = true; 180 | 181 | Log.i(file + " executed succesfully."); 182 | } 183 | } 184 | catch (NumberFormatException e) { 185 | Log.w("Skipping invalidly named file: " + file, e); 186 | } 187 | } 188 | db.setTransactionSuccessful(); 189 | } 190 | finally { 191 | db.endTransaction(); 192 | } 193 | } 194 | catch (IOException e) { 195 | Log.e("Failed to execute migrations.", e); 196 | } 197 | 198 | return migrationExecuted; 199 | } 200 | 201 | private void executeSqlScript(SQLiteDatabase db, String file) { 202 | 203 | InputStream stream = null; 204 | 205 | try { 206 | stream = Cache.getContext().getAssets().open(MIGRATION_PATH + "/" + file); 207 | 208 | if (Configuration.SQL_PARSER_DELIMITED.equalsIgnoreCase(mSqlParser)) { 209 | executeDelimitedSqlScript(db, stream); 210 | 211 | } else { 212 | executeLegacySqlScript(db, stream); 213 | 214 | } 215 | 216 | } catch (IOException e) { 217 | Log.e("Failed to execute " + file, e); 218 | 219 | } finally { 220 | IOUtils.closeQuietly(stream); 221 | 222 | } 223 | } 224 | 225 | private void executeDelimitedSqlScript(SQLiteDatabase db, InputStream stream) throws IOException { 226 | 227 | List commands = SqlParser.parse(stream); 228 | 229 | for(String command : commands) { 230 | try { 231 | db.execSQL(command); 232 | } catch(Exception ex){ 233 | ex.printStackTrace(); 234 | } 235 | } 236 | } 237 | 238 | private void executeLegacySqlScript(SQLiteDatabase db, InputStream stream) throws IOException { 239 | 240 | InputStreamReader reader = null; 241 | BufferedReader buffer = null; 242 | 243 | try { 244 | reader = new InputStreamReader(stream); 245 | buffer = new BufferedReader(reader); 246 | String line = null; 247 | 248 | while ((line = buffer.readLine()) != null) { 249 | line = line.replace(";", "").trim(); 250 | if (!TextUtils.isEmpty(line)) { 251 | try { 252 | db.execSQL(line); 253 | } catch(Exception ex){ 254 | ex.printStackTrace(); 255 | } 256 | } 257 | } 258 | 259 | } finally { 260 | IOUtils.closeQuietly(buffer); 261 | IOUtils.closeQuietly(reader); 262 | 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/com/activeandroid/Interface.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid; 2 | 3 | /** 4 | * Created by Victor on 30/10/2015. 5 | */ 6 | public interface Interface { 7 | 8 | T getObject(T s); 9 | } 10 | -------------------------------------------------------------------------------- /src/com/activeandroid/ModelInfo.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.net.URL; 22 | import java.util.ArrayList; 23 | import java.util.Calendar; 24 | import java.util.Collection; 25 | import java.util.Enumeration; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | import android.content.Context; 31 | 32 | import com.activeandroid.serializer.CalendarSerializer; 33 | import com.activeandroid.serializer.SqlDateSerializer; 34 | import com.activeandroid.serializer.TypeSerializer; 35 | import com.activeandroid.serializer.UtilDateSerializer; 36 | import com.activeandroid.serializer.FileSerializer; 37 | import com.activeandroid.util.Log; 38 | import com.activeandroid.util.ReflectionUtils; 39 | import dalvik.system.DexFile; 40 | 41 | final class ModelInfo { 42 | ////////////////////////////////////////////////////////////////////////////////////// 43 | // PRIVATE METHODS 44 | ////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | private Map, TableInfo> mTableInfos = new HashMap, TableInfo>(); 47 | private Map, TypeSerializer> mTypeSerializers = new HashMap, TypeSerializer>() { 48 | { 49 | put(Calendar.class, new CalendarSerializer()); 50 | put(java.sql.Date.class, new SqlDateSerializer()); 51 | put(java.util.Date.class, new UtilDateSerializer()); 52 | put(java.io.File.class, new FileSerializer()); 53 | } 54 | }; 55 | 56 | ////////////////////////////////////////////////////////////////////////////////////// 57 | // CONSTRUCTORS 58 | ////////////////////////////////////////////////////////////////////////////////////// 59 | 60 | public ModelInfo(Configuration configuration) { 61 | if (!loadModelFromMetaData(configuration)) { 62 | try { 63 | scanForModel(configuration.getContext()); 64 | } 65 | catch (IOException e) { 66 | Log.e("Couldn't open source path.", e); 67 | } 68 | } 69 | 70 | Log.i("ModelInfo loaded."); 71 | } 72 | 73 | ////////////////////////////////////////////////////////////////////////////////////// 74 | // PUBLIC METHODS 75 | ////////////////////////////////////////////////////////////////////////////////////// 76 | 77 | public Collection getTableInfos() { 78 | return mTableInfos.values(); 79 | } 80 | 81 | public TableInfo getTableInfo(Class type) { 82 | return mTableInfos.get(type); 83 | } 84 | 85 | public TypeSerializer getTypeSerializer(Class type) { 86 | return mTypeSerializers.get(type); 87 | } 88 | 89 | ////////////////////////////////////////////////////////////////////////////////////// 90 | // PRIVATE METHODS 91 | ////////////////////////////////////////////////////////////////////////////////////// 92 | 93 | private boolean loadModelFromMetaData(Configuration configuration) { 94 | if (!configuration.isValid()) { 95 | return false; 96 | } 97 | 98 | final List> models = configuration.getModelClasses(); 99 | if (models != null) { 100 | for (Class model : models) { 101 | mTableInfos.put(model, new TableInfo(model)); 102 | } 103 | } 104 | 105 | final List> typeSerializers = configuration.getTypeSerializers(); 106 | if (typeSerializers != null) { 107 | for (Class typeSerializer : typeSerializers) { 108 | try { 109 | TypeSerializer instance = typeSerializer.newInstance(); 110 | mTypeSerializers.put(instance.getDeserializedType(), instance); 111 | } 112 | catch (InstantiationException e) { 113 | Log.e("Couldn't instantiate TypeSerializer.", e); 114 | } 115 | catch (IllegalAccessException e) { 116 | Log.e("IllegalAccessException", e); 117 | } 118 | } 119 | } 120 | 121 | return true; 122 | } 123 | 124 | private void scanForModel(Context context) throws IOException { 125 | String packageName = context.getPackageName(); 126 | String sourcePath = context.getApplicationInfo().sourceDir; 127 | List paths = new ArrayList(); 128 | 129 | if (sourcePath != null && !(new File(sourcePath).isDirectory())) { 130 | DexFile dexfile = new DexFile(sourcePath); 131 | Enumeration entries = dexfile.entries(); 132 | 133 | while (entries.hasMoreElements()) { 134 | paths.add(entries.nextElement()); 135 | } 136 | } 137 | // Robolectric fallback 138 | else { 139 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 140 | Enumeration resources = classLoader.getResources(""); 141 | 142 | while (resources.hasMoreElements()) { 143 | String path = resources.nextElement().getFile(); 144 | if (path.contains("bin") || path.contains("classes")) { 145 | paths.add(path); 146 | } 147 | } 148 | } 149 | 150 | for (String path : paths) { 151 | File file = new File(path); 152 | scanForModelClasses(file, packageName, context.getClassLoader()); 153 | } 154 | } 155 | 156 | private void scanForModelClasses(File path, String packageName, ClassLoader classLoader) { 157 | if (path.isDirectory()) { 158 | for (File file : path.listFiles()) { 159 | scanForModelClasses(file, packageName, classLoader); 160 | } 161 | } 162 | else { 163 | String className = path.getName(); 164 | 165 | // Robolectric fallback 166 | if (!path.getPath().equals(className)) { 167 | className = path.getPath(); 168 | 169 | if (className.endsWith(".class")) { 170 | className = className.substring(0, className.length() - 6); 171 | } 172 | else { 173 | return; 174 | } 175 | 176 | className = className.replace(System.getProperty("file.separator"), "."); 177 | 178 | int packageNameIndex = className.lastIndexOf(packageName); 179 | if (packageNameIndex < 0) { 180 | return; 181 | } 182 | 183 | className = className.substring(packageNameIndex); 184 | } 185 | 186 | try { 187 | Class discoveredClass = Class.forName(className, false, classLoader); 188 | if (ReflectionUtils.isModel(discoveredClass)) { 189 | @SuppressWarnings("unchecked") 190 | Class modelClass = (Class) discoveredClass; 191 | mTableInfos.put(modelClass, new TableInfo(modelClass)); 192 | } 193 | else if (ReflectionUtils.isTypeSerializer(discoveredClass)) { 194 | TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance(); 195 | mTypeSerializers.put(instance.getDeserializedType(), instance); 196 | } 197 | } 198 | catch (ClassNotFoundException e) { 199 | Log.e("Couldn't create class.", e); 200 | } 201 | catch (InstantiationException e) { 202 | Log.e("Couldn't instantiate TypeSerializer.", e); 203 | } 204 | catch (IllegalAccessException e) { 205 | Log.e("IllegalAccessException", e); 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/com/activeandroid/TableInfo.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.lang.reflect.Field; 20 | import java.util.Collection; 21 | import java.util.Collections; 22 | import java.util.LinkedHashMap; 23 | import java.util.LinkedList; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import android.text.TextUtils; 28 | import android.util.Log; 29 | 30 | import com.activeandroid.annotation.Column; 31 | import com.activeandroid.annotation.Table; 32 | import com.activeandroid.util.ReflectionUtils; 33 | 34 | public final class TableInfo { 35 | ////////////////////////////////////////////////////////////////////////////////////// 36 | // PRIVATE MEMBERS 37 | ////////////////////////////////////////////////////////////////////////////////////// 38 | 39 | private Class mType; 40 | private String mTableName; 41 | private String mIdName = Table.DEFAULT_ID_NAME; 42 | 43 | private Map mColumnNames = new LinkedHashMap(); 44 | 45 | ////////////////////////////////////////////////////////////////////////////////////// 46 | // CONSTRUCTORS 47 | ////////////////////////////////////////////////////////////////////////////////////// 48 | 49 | public TableInfo(Class type) { 50 | mType = type; 51 | 52 | final Table tableAnnotation = type.getAnnotation(Table.class); 53 | 54 | if (tableAnnotation != null) { 55 | mTableName = tableAnnotation.name(); 56 | mIdName = tableAnnotation.id(); 57 | } 58 | else { 59 | mTableName = type.getSimpleName(); 60 | } 61 | 62 | // Manually add the id column since it is not declared like the other columns. 63 | Field idField = getIdField(type); 64 | mColumnNames.put(idField, mIdName); 65 | 66 | List fields = new LinkedList(ReflectionUtils.getDeclaredColumnFields(type)); 67 | Collections.reverse(fields); 68 | 69 | for (Field field : fields) { 70 | if (field.isAnnotationPresent(Column.class)) { 71 | final Column columnAnnotation = field.getAnnotation(Column.class); 72 | String columnName = columnAnnotation.name(); 73 | if (TextUtils.isEmpty(columnName)) { 74 | columnName = field.getName(); 75 | } 76 | 77 | mColumnNames.put(field, columnName); 78 | } 79 | } 80 | 81 | } 82 | 83 | ////////////////////////////////////////////////////////////////////////////////////// 84 | // PUBLIC METHODS 85 | ////////////////////////////////////////////////////////////////////////////////////// 86 | 87 | public Class getType() { 88 | return mType; 89 | } 90 | 91 | public String getTableName() { 92 | return mTableName; 93 | } 94 | 95 | public String getIdName() { 96 | return mIdName; 97 | } 98 | 99 | public Collection getFields() { 100 | return mColumnNames.keySet(); 101 | } 102 | 103 | public String getColumnName(Field field) { 104 | return mColumnNames.get(field); 105 | } 106 | 107 | 108 | private Field getIdField(Class type) { 109 | if (type.equals(Model.class)) { 110 | try { 111 | return type.getDeclaredField("mId"); 112 | } 113 | catch (NoSuchFieldException e) { 114 | Log.e("Impossible!", e.toString()); 115 | } 116 | } 117 | else if (type.getSuperclass() != null) { 118 | return getIdField(type.getSuperclass()); 119 | } 120 | 121 | return null; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/com/activeandroid/annotation/Column.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.annotation; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Target(ElementType.FIELD) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Column { 27 | public enum ConflictAction { 28 | ROLLBACK, ABORT, FAIL, IGNORE, REPLACE 29 | } 30 | 31 | public enum ForeignKeyAction { 32 | SET_NULL, SET_DEFAULT, CASCADE, RESTRICT, NO_ACTION 33 | } 34 | 35 | public String name() default ""; 36 | 37 | public int length() default -1; 38 | 39 | public boolean notNull() default false; 40 | 41 | public ConflictAction onNullConflict() default ConflictAction.FAIL; 42 | 43 | public ForeignKeyAction onDelete() default ForeignKeyAction.NO_ACTION; 44 | 45 | public ForeignKeyAction onUpdate() default ForeignKeyAction.NO_ACTION; 46 | 47 | public boolean unique() default false; 48 | 49 | public ConflictAction onUniqueConflict() default ConflictAction.FAIL; 50 | 51 | /* 52 | * If set uniqueGroups = {"group_name"}, we will create a table constraint with group. 53 | * 54 | * Example: 55 | * 56 | * @Table(name = "table_name") 57 | * public class Table extends Model { 58 | * @Column(name = "member1", uniqueGroups = {"group1"}, onUniqueConflicts = {ConflictAction.FAIL}) 59 | * public String member1; 60 | * 61 | * @Column(name = "member2", uniqueGroups = {"group1", "group2"}, onUniqueConflicts = {ConflictAction.FAIL, ConflictAction.IGNORE}) 62 | * public String member2; 63 | * 64 | * @Column(name = "member3", uniqueGroups = {"group2"}, onUniqueConflicts = {ConflictAction.IGNORE}) 65 | * public String member3; 66 | * } 67 | * 68 | * CREATE TABLE table_name (..., UNIQUE (member1, member2) ON CONFLICT FAIL, UNIQUE (member2, member3) ON CONFLICT IGNORE) 69 | */ 70 | public String[] uniqueGroups() default {}; 71 | 72 | public ConflictAction[] onUniqueConflicts() default {}; 73 | 74 | /* 75 | * If set index = true, we will create a index with single column. 76 | * 77 | * Example: 78 | * 79 | * @Table(name = "table_name") 80 | * public class Table extends Model { 81 | * @Column(name = "member", index = true) 82 | * public String member; 83 | * } 84 | * 85 | * Execute CREATE INDEX index_table_name_member on table_name(member) 86 | */ 87 | public boolean index() default false; 88 | 89 | /* 90 | * If set indexGroups = {"group_name"}, we will create a index with group. 91 | * 92 | * Example: 93 | * 94 | * @Table(name = "table_name") 95 | * public class Table extends Model { 96 | * @Column(name = "member1", indexGroups = {"group1"}) 97 | * public String member1; 98 | * 99 | * @Column(name = "member2", indexGroups = {"group1", "group2"}) 100 | * public String member2; 101 | * 102 | * @Column(name = "member3", indexGroups = {"group2"}) 103 | * public String member3; 104 | * } 105 | * 106 | * Execute CREATE INDEX index_table_name_group1 on table_name(member1, member2) 107 | * Execute CREATE INDEX index_table_name_group2 on table_name(member2, member3) 108 | */ 109 | public String[] indexGroups() default {}; 110 | } 111 | -------------------------------------------------------------------------------- /src/com/activeandroid/annotation/Table.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.annotation; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Target(ElementType.TYPE) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Table { 27 | 28 | public static final String DEFAULT_ID_NAME = "Id"; 29 | public String name(); 30 | public String id() default DEFAULT_ID_NAME; 31 | } 32 | -------------------------------------------------------------------------------- /src/com/activeandroid/app/Application.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.app; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.ActiveAndroid; 20 | 21 | public class Application extends android.app.Application { 22 | @Override 23 | public void onCreate() { 24 | super.onCreate(); 25 | ActiveAndroid.initialize(this); 26 | } 27 | 28 | @Override 29 | public void onTerminate() { 30 | super.onTerminate(); 31 | ActiveAndroid.dispose(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/com/activeandroid/content/ContentProvider.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.content; 2 | 3 | import android.content.ContentValues; 4 | import android.content.UriMatcher; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.util.SparseArray; 8 | 9 | import com.activeandroid.ActiveAndroid; 10 | import com.activeandroid.Cache; 11 | import com.activeandroid.Configuration; 12 | import com.activeandroid.Model; 13 | import com.activeandroid.TableInfo; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class ContentProvider extends android.content.ContentProvider { 19 | ////////////////////////////////////////////////////////////////////////////////////// 20 | // PRIVATE CONSTANTS 21 | ////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 24 | private static final SparseArray> TYPE_CODES = new SparseArray>(); 25 | 26 | ////////////////////////////////////////////////////////////////////////////////////// 27 | // PRIVATE MEMBERS 28 | ////////////////////////////////////////////////////////////////////////////////////// 29 | 30 | private static String sAuthority; 31 | private static SparseArray sMimeTypeCache = new SparseArray(); 32 | 33 | ////////////////////////////////////////////////////////////////////////////////////// 34 | // PUBLIC METHODS 35 | ////////////////////////////////////////////////////////////////////////////////////// 36 | 37 | @Override 38 | public boolean onCreate() { 39 | ActiveAndroid.initialize(getConfiguration()); 40 | sAuthority = getAuthority(); 41 | 42 | final List tableInfos = new ArrayList(Cache.getTableInfos()); 43 | final int size = tableInfos.size(); 44 | for (int i = 0; i < size; i++) { 45 | final TableInfo tableInfo = tableInfos.get(i); 46 | final int tableKey = (i * 2) + 1; 47 | final int itemKey = (i * 2) + 2; 48 | 49 | // content:/// 50 | URI_MATCHER.addURI(sAuthority, tableInfo.getTableName().toLowerCase(), tableKey); 51 | TYPE_CODES.put(tableKey, tableInfo.getType()); 52 | 53 | // content:///
/ 54 | URI_MATCHER.addURI(sAuthority, tableInfo.getTableName().toLowerCase() + "/#", itemKey); 55 | TYPE_CODES.put(itemKey, tableInfo.getType()); 56 | } 57 | 58 | return true; 59 | } 60 | 61 | @Override 62 | public String getType(Uri uri) { 63 | final int match = URI_MATCHER.match(uri); 64 | 65 | String cachedMimeType = sMimeTypeCache.get(match); 66 | if (cachedMimeType != null) { 67 | return cachedMimeType; 68 | } 69 | 70 | final Class type = getModelType(uri); 71 | final boolean single = ((match % 2) == 0); 72 | 73 | StringBuilder mimeType = new StringBuilder(); 74 | mimeType.append("vnd"); 75 | mimeType.append("."); 76 | mimeType.append(sAuthority); 77 | mimeType.append("."); 78 | mimeType.append(single ? "item" : "dir"); 79 | mimeType.append("/"); 80 | mimeType.append("vnd"); 81 | mimeType.append("."); 82 | mimeType.append(sAuthority); 83 | mimeType.append("."); 84 | mimeType.append(Cache.getTableName(type)); 85 | 86 | sMimeTypeCache.append(match, mimeType.toString()); 87 | 88 | return mimeType.toString(); 89 | } 90 | 91 | // SQLite methods 92 | 93 | @Override 94 | public Uri insert(Uri uri, ContentValues values) { 95 | final Class type = getModelType(uri); 96 | final Long id = Cache.openDatabase().insert(Cache.getTableName(type), values); 97 | 98 | if (id != null && id > 0) { 99 | Uri retUri = createUri(type, id); 100 | notifyChange(retUri); 101 | 102 | return retUri; 103 | } 104 | 105 | return null; 106 | } 107 | 108 | @Override 109 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 110 | final Class type = getModelType(uri); 111 | final int count = Cache.openDatabase().update(Cache.getTableName(type), values, selection, selectionArgs); 112 | 113 | notifyChange(uri); 114 | 115 | return count; 116 | } 117 | 118 | @Override 119 | public int delete(Uri uri, String selection, String[] selectionArgs) { 120 | final Class type = getModelType(uri); 121 | final int count = Cache.openDatabase().delete(Cache.getTableName(type), selection, selectionArgs); 122 | 123 | notifyChange(uri); 124 | 125 | return count; 126 | } 127 | 128 | @Override 129 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { 130 | final Class type = getModelType(uri); 131 | final Cursor cursor = Cache.openDatabase().query( 132 | Cache.getTableName(type), 133 | projection, 134 | selection, 135 | selectionArgs, 136 | null, 137 | null, 138 | sortOrder); 139 | 140 | cursor.setNotificationUri(getContext().getContentResolver(), uri); 141 | 142 | return cursor; 143 | } 144 | 145 | ////////////////////////////////////////////////////////////////////////////////////// 146 | // PUBLIC METHODS 147 | ////////////////////////////////////////////////////////////////////////////////////// 148 | 149 | public static Uri createUri(Class type, Long id) { 150 | final StringBuilder uri = new StringBuilder(); 151 | uri.append("content://"); 152 | uri.append(sAuthority); 153 | uri.append("/"); 154 | uri.append(Cache.getTableName(type).toLowerCase()); 155 | 156 | if (id != null) { 157 | uri.append("/"); 158 | uri.append(id.toString()); 159 | } 160 | 161 | return Uri.parse(uri.toString()); 162 | } 163 | 164 | ////////////////////////////////////////////////////////////////////////////////////// 165 | // PROTECTED METHODS 166 | ////////////////////////////////////////////////////////////////////////////////////// 167 | 168 | protected String getAuthority() { 169 | return getContext().getPackageName(); 170 | } 171 | 172 | protected Configuration getConfiguration() { 173 | return new Configuration.Builder(getContext()).create(); 174 | } 175 | 176 | ////////////////////////////////////////////////////////////////////////////////////// 177 | // PRIVATE METHODS 178 | ////////////////////////////////////////////////////////////////////////////////////// 179 | 180 | private Class getModelType(Uri uri) { 181 | final int code = URI_MATCHER.match(uri); 182 | if (code != UriMatcher.NO_MATCH) { 183 | return TYPE_CODES.get(code); 184 | } 185 | 186 | return null; 187 | } 188 | 189 | private void notifyChange(Uri uri) { 190 | getContext().getContentResolver().notifyChange(uri, null); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/com/activeandroid/query/Delete.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.Model; 20 | 21 | public final class Delete implements Sqlable { 22 | public Delete() { 23 | } 24 | 25 | public From from(Class table) { 26 | return new From(table, this); 27 | } 28 | 29 | @Override 30 | public String toSql() { 31 | return "DELETE "; 32 | } 33 | } -------------------------------------------------------------------------------- /src/com/activeandroid/query/Join.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.text.TextUtils; 20 | 21 | import com.activeandroid.Cache; 22 | import com.activeandroid.Model; 23 | 24 | public final class Join implements Sqlable { 25 | static enum JoinType { 26 | LEFT, OUTER, INNER, CROSS 27 | } 28 | 29 | private From mFrom; 30 | private Class mType; 31 | private String mAlias; 32 | private JoinType mJoinType; 33 | private String mOn; 34 | private String[] mUsing; 35 | 36 | Join(From from, Class table, JoinType joinType) { 37 | mFrom = from; 38 | mType = table; 39 | mJoinType = joinType; 40 | } 41 | 42 | public Join as(String alias) { 43 | mAlias = alias; 44 | return this; 45 | } 46 | 47 | public From on(String on) { 48 | mOn = on; 49 | return mFrom; 50 | } 51 | 52 | public From on(String on, Object... args) { 53 | mOn = on; 54 | mFrom.addArguments(args); 55 | return mFrom; 56 | } 57 | 58 | public From using(String... columns) { 59 | mUsing = columns; 60 | return mFrom; 61 | } 62 | 63 | @Override 64 | public String toSql() { 65 | StringBuilder sql = new StringBuilder(); 66 | 67 | if (mJoinType != null) { 68 | sql.append(mJoinType.toString()).append(" "); 69 | } 70 | 71 | sql.append("JOIN "); 72 | sql.append(Cache.getTableName(mType)); 73 | sql.append(" "); 74 | 75 | if (mAlias != null) { 76 | sql.append("AS "); 77 | sql.append(mAlias); 78 | sql.append(" "); 79 | } 80 | 81 | if (mOn != null) { 82 | sql.append("ON "); 83 | sql.append(mOn); 84 | sql.append(" "); 85 | } 86 | else if (mUsing != null) { 87 | sql.append("USING ("); 88 | sql.append(TextUtils.join(", ", mUsing)); 89 | sql.append(") "); 90 | } 91 | 92 | return sql.toString(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/com/activeandroid/query/Select.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.text.TextUtils; 20 | 21 | import com.activeandroid.Model; 22 | 23 | public final class Select implements Sqlable { 24 | private String[] mColumns; 25 | private boolean mDistinct = false; 26 | private boolean mAll = false; 27 | 28 | public Select() { 29 | } 30 | 31 | public Select(String... columns) { 32 | mColumns = columns; 33 | } 34 | 35 | public Select(Column... columns) { 36 | final int size = columns.length; 37 | mColumns = new String[size]; 38 | for (int i = 0; i < size; i++) { 39 | mColumns[i] = columns[i].name + " AS " + columns[i].alias; 40 | } 41 | } 42 | 43 | public Select distinct() { 44 | mDistinct = true; 45 | mAll = false; 46 | 47 | return this; 48 | } 49 | 50 | public Select all() { 51 | mDistinct = false; 52 | mAll = true; 53 | 54 | return this; 55 | } 56 | 57 | public From from(Class table) { 58 | return new From(table, this); 59 | } 60 | 61 | public static class Column { 62 | String name; 63 | String alias; 64 | 65 | public Column(String name, String alias) { 66 | this.name = name; 67 | this.alias = alias; 68 | } 69 | } 70 | 71 | @Override 72 | public String toSql() { 73 | StringBuilder sql = new StringBuilder(); 74 | 75 | sql.append("SELECT "); 76 | 77 | if (mDistinct) { 78 | sql.append("DISTINCT "); 79 | } 80 | else if (mAll) { 81 | sql.append("ALL "); 82 | } 83 | 84 | if (mColumns != null && mColumns.length > 0) { 85 | sql.append(TextUtils.join(", ", mColumns) + " "); 86 | } 87 | else { 88 | sql.append("* "); 89 | } 90 | 91 | return sql.toString(); 92 | } 93 | } -------------------------------------------------------------------------------- /src/com/activeandroid/query/Set.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.util.SQLiteUtils; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | public final class Set implements Sqlable { 26 | private Update mUpdate; 27 | 28 | private String mSet; 29 | private String mWhere; 30 | 31 | private List mSetArguments; 32 | private List mWhereArguments; 33 | 34 | public Set(Update queryBase, String set) { 35 | mUpdate = queryBase; 36 | mSet = set; 37 | 38 | mSetArguments = new ArrayList(); 39 | mWhereArguments = new ArrayList(); 40 | } 41 | 42 | public Set(Update queryBase, String set, Object... args) { 43 | mUpdate = queryBase; 44 | mSet = set; 45 | 46 | mSetArguments = new ArrayList(); 47 | mWhereArguments = new ArrayList(); 48 | 49 | mSetArguments.addAll(Arrays.asList(args)); 50 | } 51 | 52 | public Set where(String where) { 53 | mWhere = where; 54 | mWhereArguments.clear(); 55 | 56 | return this; 57 | } 58 | 59 | public Set where(String where, Object... args) { 60 | mWhere = where; 61 | mWhereArguments.clear(); 62 | mWhereArguments.addAll(Arrays.asList(args)); 63 | 64 | return this; 65 | } 66 | 67 | @Override 68 | public String toSql() { 69 | StringBuilder sql = new StringBuilder(); 70 | sql.append(mUpdate.toSql()); 71 | sql.append("SET "); 72 | sql.append(mSet); 73 | sql.append(" "); 74 | 75 | if (mWhere != null) { 76 | sql.append("WHERE "); 77 | sql.append(mWhere); 78 | sql.append(" "); 79 | } 80 | 81 | return sql.toString(); 82 | } 83 | 84 | public void execute() { 85 | SQLiteUtils.execSql(toSql(), getArguments()); 86 | } 87 | 88 | public String[] getArguments() { 89 | final int setSize = mSetArguments.size(); 90 | final int whereSize = mWhereArguments.size(); 91 | final String[] args = new String[setSize + whereSize]; 92 | 93 | for (int i = 0; i < setSize; i++) { 94 | args[i] = mSetArguments.get(i).toString(); 95 | } 96 | 97 | for (int i = 0; i < whereSize; i++) { 98 | args[i + setSize] = mWhereArguments.get(i).toString(); 99 | } 100 | 101 | return args; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/com/activeandroid/query/Sqlable.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | public interface Sqlable { 20 | public String toSql(); 21 | } -------------------------------------------------------------------------------- /src/com/activeandroid/query/Update.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.Cache; 20 | import com.activeandroid.Model; 21 | 22 | public final class Update implements Sqlable { 23 | private Class mType; 24 | 25 | public Update(Class table) { 26 | mType = table; 27 | } 28 | 29 | public Set set(String set) { 30 | return new Set(this, set); 31 | } 32 | 33 | public Set set(String set, Object... args) { 34 | return new Set(this, set, args); 35 | } 36 | 37 | Class getType() { 38 | return mType; 39 | } 40 | 41 | @Override 42 | public String toSql() { 43 | StringBuilder sql = new StringBuilder(); 44 | sql.append("UPDATE "); 45 | sql.append(Cache.getTableName(mType)); 46 | sql.append(" "); 47 | 48 | return sql.toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/activeandroid/rx/RxSelect.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.rx; 2 | 3 | 4 | import android.database.Cursor; 5 | import android.text.TextUtils; 6 | 7 | import com.activeandroid.Cache; 8 | import com.activeandroid.Model; 9 | import com.activeandroid.rxschedulers.AndroidSchedulers; 10 | import com.activeandroid.sqlbrite.SqlBrite; 11 | import com.activeandroid.util.SQLiteUtils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import rx.Observable; 17 | import rx.functions.Func1; 18 | import rx.schedulers.Schedulers; 19 | 20 | /** 21 | * Created by Victor on 30/10/2015. 22 | */ 23 | public class RxSelect { 24 | 25 | private Class mType; 26 | private String mAlias; 27 | private StringBuilder mWhere = new StringBuilder(); 28 | private String mLimit; 29 | private String mGroupBy; 30 | private String mHaving; 31 | private String mOrderBy; 32 | private String mOffset; 33 | private List mArguments; 34 | 35 | private RxSelect(Class type) { 36 | this.mType = type; 37 | mArguments = new ArrayList<>(); 38 | } 39 | 40 | public static RxSelect from(Class type) { 41 | return new RxSelect<>(type); 42 | } 43 | 44 | public RxSelect as(String alias) { 45 | mAlias = alias; 46 | return this; 47 | } 48 | 49 | public RxSelect groupBy(String groupBy) { 50 | mGroupBy = groupBy; 51 | return this; 52 | } 53 | 54 | public RxSelect where(String clause) { 55 | // Chain conditions if a previous condition exists. 56 | if (mWhere.length() > 0) { 57 | mWhere.append(" AND "); 58 | } 59 | mWhere.append(clause); 60 | return this; 61 | } 62 | 63 | public RxSelect where(String clause, Object... args) { 64 | where(clause).addArguments(args); 65 | return this; 66 | } 67 | 68 | public RxSelect orderBy(String orderBy) { 69 | mOrderBy = orderBy; 70 | return this; 71 | } 72 | 73 | public RxSelect limit(int limit) { 74 | return limit(String.valueOf(limit)); 75 | } 76 | 77 | public RxSelect limit(String limit) { 78 | mLimit = limit; 79 | return this; 80 | } 81 | 82 | public RxSelect offset(int offset) { 83 | return offset(String.valueOf(offset)); 84 | } 85 | 86 | public RxSelect offset(String offset) { 87 | mOffset = offset; 88 | return this; 89 | } 90 | 91 | public RxSelect having(String having) { 92 | mHaving = having; 93 | return this; 94 | } 95 | 96 | public rx.Observable> execute() { 97 | 98 | String sql = buildSql(); 99 | 100 | return Cache.openDatabase().createQuery(Cache.getTableName(mType), sql, getArguments()) 101 | .subscribeOn(Schedulers.io()) 102 | .map(new Func1>() { 103 | @Override 104 | public List call(SqlBrite.Query query) { 105 | try { 106 | Cursor cursor = query.run(); 107 | List result = SQLiteUtils.processCursor(mType, cursor); 108 | cursor.close(); 109 | return result; 110 | } catch (Exception ex) { 111 | ex.printStackTrace(); 112 | } 113 | 114 | return null; 115 | 116 | } 117 | }) 118 | .observeOn(AndroidSchedulers.mainThread()); 119 | } 120 | 121 | public rx.Observable executeSingle() { 122 | 123 | String sql = buildSql(); 124 | 125 | return Cache.openDatabase().createQuery(Cache.getTableName(mType), sql, getArguments()) 126 | .subscribeOn(Schedulers.io()) 127 | .map(new Func1() { 128 | @Override 129 | public T call(SqlBrite.Query query) { 130 | try { 131 | Cursor cursor = query.run(); 132 | if (cursor != null && cursor.getCount() > 0) { 133 | cursor.moveToFirst(); 134 | Model model = (Model) mType.newInstance(); 135 | model.loadFromCursor(cursor); 136 | cursor.close(); 137 | return (T) model; 138 | } else if(cursor != null) { 139 | cursor.close(); 140 | } 141 | } catch (Exception ex) { 142 | ex.printStackTrace(); 143 | } 144 | 145 | return null; 146 | 147 | } 148 | }) 149 | .observeOn(AndroidSchedulers.mainThread()); 150 | } 151 | 152 | private String buildSql() { 153 | StringBuilder sql = new StringBuilder(); 154 | 155 | buildSelect(sql); 156 | addFrom(sql); 157 | addWhere(sql); 158 | addGroupBy(sql); 159 | addHaving(sql); 160 | addOrderBy(sql); 161 | addLimit(sql); 162 | addOffset(sql); 163 | 164 | return sqlString(sql); 165 | } 166 | 167 | public String toCountSql() { 168 | 169 | final StringBuilder sql = new StringBuilder(); 170 | sql.append("SELECT COUNT(*) "); 171 | 172 | addFrom(sql); 173 | addWhere(sql); 174 | addGroupBy(sql); 175 | addHaving(sql); 176 | addLimit(sql); 177 | addOffset(sql); 178 | 179 | return sqlString(sql); 180 | } 181 | 182 | private void buildSelect(final StringBuilder sql) { 183 | sql.append("SELECT * "); 184 | } 185 | 186 | private void addFrom(final StringBuilder sql) { 187 | sql.append("FROM "); 188 | sql.append(Cache.getTableName(mType)).append(" "); 189 | 190 | if (mAlias != null) { 191 | sql.append("AS "); 192 | sql.append(mAlias); 193 | sql.append(" "); 194 | } 195 | } 196 | 197 | private void addWhere(final StringBuilder sql) { 198 | if (!TextUtils.isEmpty(mWhere)) { 199 | sql.append("WHERE "); 200 | sql.append(mWhere); 201 | sql.append(" "); 202 | } 203 | } 204 | 205 | private void addGroupBy(final StringBuilder sql) { 206 | if (mGroupBy != null) { 207 | sql.append("GROUP BY "); 208 | sql.append(mGroupBy); 209 | sql.append(" "); 210 | } 211 | } 212 | 213 | private void addHaving(final StringBuilder sql) { 214 | if (mHaving != null) { 215 | sql.append("HAVING "); 216 | sql.append(mHaving); 217 | sql.append(" "); 218 | } 219 | } 220 | 221 | private void addOrderBy(final StringBuilder sql) { 222 | if (mOrderBy != null) { 223 | sql.append("ORDER BY "); 224 | sql.append(mOrderBy); 225 | sql.append(" "); 226 | } 227 | } 228 | 229 | private void addLimit(final StringBuilder sql) { 230 | if (!TextUtils.isEmpty(mLimit)) { 231 | sql.append("LIMIT "); 232 | sql.append(mLimit); 233 | sql.append(" "); 234 | } 235 | } 236 | 237 | private void addOffset(final StringBuilder sql) { 238 | if (mOffset != null) { 239 | sql.append("OFFSET "); 240 | sql.append(mOffset); 241 | sql.append(" "); 242 | } 243 | } 244 | 245 | /** 246 | * Gets the number of rows returned by the query. 247 | */ 248 | public Observable count() { 249 | return Cache.openDatabase().createQuery(Cache.getTableName(mType), toCountSql(), getArguments()) 250 | .subscribeOn(Schedulers.io()) 251 | .map(new Func1() { 252 | @Override public Integer call(SqlBrite.Query query) { 253 | Cursor cursor = query.run(); 254 | if (cursor.moveToFirst()) { 255 | int amount = cursor.getInt(cursor.getColumnIndex(cursor.getColumnName(0))); 256 | cursor.close(); 257 | return amount; 258 | } 259 | return 0; 260 | } 261 | }) 262 | .observeOn(AndroidSchedulers.mainThread()); 263 | } 264 | 265 | private String sqlString(final StringBuilder sql) { 266 | return sql.toString().trim(); 267 | } 268 | 269 | void addArguments(Object[] args) { 270 | for(Object arg : args) { 271 | if (arg.getClass() == boolean.class || arg.getClass() == Boolean.class) { 272 | arg = (arg.equals(true) ? 1 : 0); 273 | } 274 | mArguments.add(arg); 275 | } 276 | } 277 | 278 | public String[] getArguments() { 279 | final int size = mArguments.size(); 280 | final String[] args = new String[size]; 281 | 282 | for (int i = 0; i < size; i++) { 283 | args[i] = mArguments.get(i).toString(); 284 | } 285 | 286 | return args; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/com/activeandroid/rxschedulers/AndroidSchedulers.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.rxschedulers; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import rx.Scheduler; 7 | 8 | /** Android-specific Schedulers. */ 9 | public final class AndroidSchedulers { 10 | private AndroidSchedulers() { 11 | throw new AssertionError("No instances"); 12 | } 13 | 14 | private static final Scheduler MAIN_THREAD_SCHEDULER = 15 | new HandlerScheduler(new Handler(Looper.getMainLooper())); 16 | 17 | /** A {@link Scheduler} which executes actions on the Android UI thread. */ 18 | public static Scheduler mainThread() { 19 | Scheduler scheduler = 20 | RxAndroidPlugins.getInstance().getSchedulersHook().getMainThreadScheduler(); 21 | return scheduler != null ? scheduler : MAIN_THREAD_SCHEDULER; 22 | } 23 | } -------------------------------------------------------------------------------- /src/com/activeandroid/rxschedulers/HandlerScheduler.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.rxschedulers; 2 | 3 | 4 | import android.os.Handler; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import rx.Scheduler; 9 | import rx.Subscription; 10 | import rx.functions.Action0; 11 | import rx.internal.schedulers.ScheduledAction; 12 | import rx.subscriptions.CompositeSubscription; 13 | import rx.subscriptions.Subscriptions; 14 | 15 | /** A {@link Scheduler} backed by a {@link Handler}. */ 16 | public final class HandlerScheduler extends Scheduler { 17 | /** Create a {@link Scheduler} which uses {@code handler} to execute actions. */ 18 | public static HandlerScheduler from(Handler handler) { 19 | if (handler == null) throw new NullPointerException("handler == null"); 20 | return new HandlerScheduler(handler); 21 | } 22 | 23 | private final Handler handler; 24 | 25 | HandlerScheduler(Handler handler) { 26 | this.handler = handler; 27 | } 28 | 29 | @Override 30 | public Worker createWorker() { 31 | return new HandlerWorker(handler); 32 | } 33 | 34 | static class HandlerWorker extends Worker { 35 | 36 | private final Handler handler; 37 | 38 | private final CompositeSubscription compositeSubscription = new CompositeSubscription(); 39 | 40 | HandlerWorker(Handler handler) { 41 | this.handler = handler; 42 | } 43 | 44 | @Override 45 | public void unsubscribe() { 46 | compositeSubscription.unsubscribe(); 47 | } 48 | 49 | @Override 50 | public boolean isUnsubscribed() { 51 | return compositeSubscription.isUnsubscribed(); 52 | } 53 | 54 | @Override 55 | public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { 56 | if (compositeSubscription.isUnsubscribed()) { 57 | return Subscriptions.unsubscribed(); 58 | } 59 | 60 | action = RxAndroidPlugins.getInstance().getSchedulersHook().onSchedule(action); 61 | 62 | final ScheduledAction scheduledAction = new ScheduledAction(action); 63 | scheduledAction.addParent(compositeSubscription); 64 | compositeSubscription.add(scheduledAction); 65 | 66 | handler.postDelayed(scheduledAction, unit.toMillis(delayTime)); 67 | 68 | scheduledAction.add(Subscriptions.create(new Action0() { 69 | @Override 70 | public void call() { 71 | handler.removeCallbacks(scheduledAction); 72 | } 73 | })); 74 | 75 | return scheduledAction; 76 | } 77 | 78 | @Override 79 | public Subscription schedule(final Action0 action) { 80 | return schedule(action, 0, TimeUnit.MILLISECONDS); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/com/activeandroid/rxschedulers/RxAndroidPlugins.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.rxschedulers; 2 | 3 | 4 | import java.util.concurrent.atomic.AtomicReference; 5 | 6 | import rx.annotations.Beta; 7 | 8 | /** 9 | * Registry for plugin implementations that allows global override and handles the retrieval of 10 | * correct implementation based on order of precedence: 11 | *
    12 | *
  1. plugin registered globally via {@code register} methods in this class
  2. 13 | *
  3. default implementation
  4. 14 | *
15 | */ 16 | public final class RxAndroidPlugins { 17 | private static final RxAndroidPlugins INSTANCE = new RxAndroidPlugins(); 18 | 19 | public static RxAndroidPlugins getInstance() { 20 | return INSTANCE; 21 | } 22 | 23 | private final AtomicReference schedulersHook = 24 | new AtomicReference(); 25 | 26 | RxAndroidPlugins() { 27 | } 28 | 29 | /** 30 | * Reset any explicit or default-set hooks. 31 | *

32 | * Note: This should only be used for testing purposes. 33 | */ 34 | @Beta 35 | public void reset() { 36 | schedulersHook.set(null); 37 | } 38 | 39 | /** 40 | * Retrieves the instance of {@link RxAndroidSchedulersHook} to use based on order of 41 | * precedence as defined in the {@link RxAndroidPlugins} class header. 42 | *

43 | * Override the default by calling {@link #registerSchedulersHook(RxAndroidSchedulersHook)} or by 44 | * setting the property {@code rxandroid.plugin.RxAndroidSchedulersHook.implementation} with the 45 | * full classname to load. 46 | */ 47 | public RxAndroidSchedulersHook getSchedulersHook() { 48 | if (schedulersHook.get() == null) { 49 | schedulersHook.compareAndSet(null, RxAndroidSchedulersHook.getDefaultInstance()); 50 | // We don't return from here but call get() again in case of thread-race so the winner will 51 | // always get returned. 52 | } 53 | return schedulersHook.get(); 54 | } 55 | 56 | /** 57 | * Registers an {@link RxAndroidSchedulersHook} implementation as a global override of any 58 | * injected or default implementations. 59 | * 60 | * @throws IllegalStateException if called more than once or after the default was initialized 61 | * (if usage occurs before trying to register) 62 | */ 63 | public void registerSchedulersHook(RxAndroidSchedulersHook impl) { 64 | if (!schedulersHook.compareAndSet(null, impl)) { 65 | throw new IllegalStateException( 66 | "Another strategy was already registered: " + schedulersHook.get()); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/com/activeandroid/rxschedulers/RxAndroidSchedulersHook.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.rxschedulers; 2 | 3 | import rx.Scheduler; 4 | import rx.functions.Action0; 5 | 6 | public class RxAndroidSchedulersHook { 7 | private static final RxAndroidSchedulersHook DEFAULT_INSTANCE = new RxAndroidSchedulersHook(); 8 | 9 | public static RxAndroidSchedulersHook getDefaultInstance() { 10 | return DEFAULT_INSTANCE; 11 | } 12 | 13 | /** 14 | * Scheduler to return from {@link AndroidSchedulers#mainThread()} or {@code null} if default 15 | * should be used. 16 | *

17 | * This instance should be or behave like a stateless singleton. 18 | */ 19 | public Scheduler getMainThreadScheduler() { 20 | return null; 21 | } 22 | 23 | /** 24 | * Invoked before the Action is handed over to the scheduler. Can be used for 25 | * wrapping/decorating/logging. The default is just a passthrough. 26 | * 27 | * @param action action to schedule 28 | * @return wrapped action to schedule 29 | */ 30 | public Action0 onSchedule(Action0 action) { 31 | return action; 32 | } 33 | } -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/BigDecimalSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public final class BigDecimalSerializer extends TypeSerializer { 6 | public Class getDeserializedType() { 7 | return BigDecimal.class; 8 | } 9 | 10 | public Class getSerializedType() { 11 | return String.class; 12 | } 13 | 14 | public String serialize(Object data) { 15 | if (data == null) { 16 | return null; 17 | } 18 | 19 | return ((BigDecimal) data).toString(); 20 | } 21 | 22 | public BigDecimal deserialize(Object data) { 23 | if (data == null) { 24 | return null; 25 | } 26 | 27 | return new BigDecimal((String) data); 28 | } 29 | } -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/CalendarSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.util.Calendar; 20 | 21 | public final class CalendarSerializer extends TypeSerializer { 22 | public Class getDeserializedType() { 23 | return Calendar.class; 24 | } 25 | 26 | public Class getSerializedType() { 27 | return long.class; 28 | } 29 | 30 | public Long serialize(Object data) { 31 | return ((Calendar) data).getTimeInMillis(); 32 | } 33 | 34 | public Calendar deserialize(Object data) { 35 | Calendar calendar = Calendar.getInstance(); 36 | calendar.setTimeInMillis((Long) data); 37 | 38 | return calendar; 39 | } 40 | } -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/FileSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | import java.io.File; 4 | 5 | /* 6 | * Copyright (C) 2010 Michael Pardo 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | 22 | public final class FileSerializer extends TypeSerializer { 23 | public Class getDeserializedType() { 24 | return File.class; 25 | } 26 | 27 | public Class getSerializedType() { 28 | return String.class; 29 | } 30 | 31 | public String serialize(Object data) { 32 | if (data == null) { 33 | return null; 34 | } 35 | 36 | return ((File) data).toString(); 37 | } 38 | 39 | public File deserialize(Object data) { 40 | if (data == null) { 41 | return null; 42 | } 43 | 44 | return new File((String) data); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/SqlDateSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.sql.Date; 20 | 21 | public final class SqlDateSerializer extends TypeSerializer { 22 | public Class getDeserializedType() { 23 | return Date.class; 24 | } 25 | 26 | public Class getSerializedType() { 27 | return long.class; 28 | } 29 | 30 | public Long serialize(Object data) { 31 | if (data == null) { 32 | return null; 33 | } 34 | 35 | return ((Date) data).getTime(); 36 | } 37 | 38 | public Date deserialize(Object data) { 39 | if (data == null) { 40 | return null; 41 | } 42 | 43 | return new Date((Long) data); 44 | } 45 | } -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/TypeSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | public abstract class TypeSerializer { 20 | public abstract Class getDeserializedType(); 21 | 22 | public abstract Class getSerializedType(); 23 | 24 | public abstract Object serialize(Object data); 25 | 26 | public abstract Object deserialize(Object data); 27 | } -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/UUIDSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | import java.util.UUID; 4 | 5 | public final class UUIDSerializer extends TypeSerializer { 6 | public Class getDeserializedType() { 7 | return UUID.class; 8 | } 9 | 10 | public Class getSerializedType() { 11 | return String.class; 12 | } 13 | 14 | public String serialize(Object data) { 15 | if (data == null) { 16 | return null; 17 | } 18 | 19 | return ((UUID) data).toString(); 20 | } 21 | 22 | public UUID deserialize(Object data) { 23 | if (data == null) { 24 | return null; 25 | } 26 | 27 | return UUID.fromString((String)data); 28 | } 29 | } -------------------------------------------------------------------------------- /src/com/activeandroid/serializer/UtilDateSerializer.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.serializer; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.util.Date; 20 | 21 | public final class UtilDateSerializer extends TypeSerializer { 22 | public Class getDeserializedType() { 23 | return Date.class; 24 | } 25 | 26 | public Class getSerializedType() { 27 | return long.class; 28 | } 29 | 30 | public Long serialize(Object data) { 31 | if (data == null) { 32 | return null; 33 | } 34 | 35 | return ((Date) data).getTime(); 36 | } 37 | 38 | public Date deserialize(Object data) { 39 | if (data == null) { 40 | return null; 41 | } 42 | 43 | return new Date((Long) data); 44 | } 45 | } -------------------------------------------------------------------------------- /src/com/activeandroid/sqlbrite/BackpressureBufferLastOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.activeandroid.sqlbrite; 17 | 18 | import rx.Observable.Operator; 19 | import rx.Producer; 20 | import rx.Subscriber; 21 | 22 | /** An operator which keeps the last emitted instance when backpressure has been applied. */ 23 | final class BackpressureBufferLastOperator implements Operator { 24 | static final Operator instance = new BackpressureBufferLastOperator<>(); 25 | 26 | static Operator instance() { 27 | //noinspection unchecked 28 | return (Operator) instance; 29 | } 30 | 31 | private BackpressureBufferLastOperator() { 32 | } 33 | 34 | @Override public Subscriber call(final Subscriber child) { 35 | BufferLastSubscriber parent = new BufferLastSubscriber<>(child); 36 | child.add(parent); 37 | child.setProducer(parent.producer); 38 | return parent; 39 | } 40 | 41 | static final class BufferLastSubscriber extends Subscriber { 42 | private static final Object NONE = new Object(); 43 | 44 | private final Subscriber child; 45 | 46 | private Object last = NONE; // Guarded by 'this'. 47 | private long requested; // Guarded by 'this'. Starts at zero. 48 | 49 | final Producer producer = new Producer() { 50 | @Override public void request(long n) { 51 | if (n < 0) { 52 | throw new IllegalArgumentException("requested " + n + " < 0"); 53 | } 54 | if (n == 0) { 55 | return; 56 | } 57 | 58 | Object candidate; 59 | synchronized (BufferLastSubscriber.this) { 60 | candidate = last; 61 | 62 | long currentRequested = requested; 63 | if (Long.MAX_VALUE - n <= currentRequested) { 64 | requested = Long.MAX_VALUE; 65 | } else { 66 | if (candidate != NONE) { 67 | n--; // Decrement since we will be emitting a value. 68 | } 69 | requested = currentRequested + n; 70 | } 71 | } 72 | 73 | // Only emit if the value is not the explicit NONE marker. 74 | if (candidate != NONE) { 75 | //noinspection unchecked 76 | child.onNext((T) candidate); 77 | } 78 | } 79 | }; 80 | 81 | public BufferLastSubscriber(Subscriber child) { 82 | this.child = child; 83 | } 84 | 85 | @Override public void onNext(T t) { 86 | boolean emit = false; 87 | synchronized (this) { 88 | long currentRequested = requested; 89 | if (currentRequested == Long.MAX_VALUE) { 90 | // No need to decrement when the firehose is open. 91 | emit = true; 92 | } else if (currentRequested > 0) { 93 | requested = currentRequested - 1; 94 | emit = true; 95 | } else { 96 | last = t; // Not emitting, store for later. 97 | } 98 | } 99 | 100 | if (emit) { 101 | child.onNext(t); 102 | } 103 | } 104 | 105 | @Override public void onStart() { 106 | request(Long.MAX_VALUE); 107 | } 108 | 109 | @Override public void onCompleted() { 110 | child.onCompleted(); 111 | } 112 | 113 | @Override public void onError(Throwable e) { 114 | child.onError(e); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/com/activeandroid/sqlbrite/BriteContentResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.activeandroid.sqlbrite; 17 | 18 | import android.content.ContentResolver; 19 | import android.database.ContentObserver; 20 | import android.database.Cursor; 21 | import android.net.Uri; 22 | import android.os.Handler; 23 | import android.os.Looper; 24 | import android.support.annotation.CheckResult; 25 | import android.support.annotation.NonNull; 26 | import android.support.annotation.Nullable; 27 | 28 | 29 | import java.util.Arrays; 30 | 31 | import rx.Observable; 32 | import rx.Observable.OnSubscribe; 33 | import rx.Subscriber; 34 | import rx.functions.Action0; 35 | import rx.subscriptions.Subscriptions; 36 | 37 | import static com.activeandroid.sqlbrite.SqlBrite.Logger; 38 | import static com.activeandroid.sqlbrite.SqlBrite.Query; 39 | 40 | /** 41 | * A lightweight wrapper around {@link ContentResolver} which allows for continuously observing 42 | * the result of a query. Create using a {@link SqlBrite} instance. 43 | */ 44 | public final class BriteContentResolver { 45 | private final Handler contentObserverHandler = new Handler(Looper.getMainLooper()); 46 | 47 | private final ContentResolver contentResolver; 48 | private final Logger logger; 49 | 50 | private volatile boolean logging; 51 | 52 | BriteContentResolver(@NonNull ContentResolver contentResolver, @NonNull Logger logger) { 53 | this.contentResolver = contentResolver; 54 | this.logger = logger; 55 | } 56 | 57 | /** Control whether debug logging is enabled. */ 58 | public void setLoggingEnabled(boolean enabled) { 59 | logging = enabled; 60 | } 61 | 62 | /** 63 | * Create an observable which will notify subscribers with a {@linkplain Query query} for 64 | * execution. Subscribers are responsible for always closing {@link Cursor} instance 65 | * returned from the {@link Query}. 66 | *

67 | * Subscribers will receive an immediate notification for initial data as well as subsequent 68 | * notifications for when the supplied {@code uri}'s data changes. Unsubscribe when you no longer 69 | * want updates to a query. 70 | *

71 | * Note: To skip the immediate notification and only receive subsequent notifications when data 72 | * has changed call {@code skip(1)} on the returned observable. 73 | *

74 | * Warning: this method does not perform the query! Only by subscribing to the returned 75 | * {@link Observable} will the operation occur. 76 | * 77 | * @see ContentResolver#query(Uri, String[], String, String[], String) 78 | * @see ContentResolver#registerContentObserver(Uri, boolean, ContentObserver) 79 | */ 80 | @CheckResult @NonNull 81 | public QueryObservable createQuery(@NonNull final Uri uri, @Nullable final String[] projection, 82 | @Nullable final String selection, @Nullable final String[] selectionArgs, @Nullable 83 | final String sortOrder, final boolean notifyForDescendents) { 84 | final Query query = new Query() { 85 | @Override public Cursor run() { 86 | return contentResolver.query(uri, projection, selection, selectionArgs, sortOrder); 87 | } 88 | }; 89 | OnSubscribe subscribe = new OnSubscribe() { 90 | @Override public void call(final Subscriber subscriber) { 91 | final ContentObserver observer = new ContentObserver(contentObserverHandler) { 92 | @Override public void onChange(boolean selfChange) { 93 | if (logging) { 94 | log("QUERY\n uri: %s\n projection: %s\n selection: %s\n selectionArgs: %s\n " 95 | + "sortOrder: %s\n notifyForDescendents: %s", uri, 96 | Arrays.toString(projection), selection, Arrays.toString(selectionArgs), sortOrder, 97 | notifyForDescendents); 98 | } 99 | subscriber.onNext(query); 100 | } 101 | }; 102 | contentResolver.registerContentObserver(uri, notifyForDescendents, observer); 103 | subscriber.add(Subscriptions.create(new Action0() { 104 | @Override public void call() { 105 | contentResolver.unregisterContentObserver(observer); 106 | } 107 | })); 108 | } 109 | }; 110 | Observable queryObservable = Observable.create(subscribe) // 111 | .startWith(query) // 112 | .lift(BackpressureBufferLastOperator.instance()); 113 | return new QueryObservable(queryObservable); 114 | } 115 | 116 | private void log(String message, Object... args) { 117 | if (args.length > 0) message = String.format(message, args); 118 | logger.log(message); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/com/activeandroid/sqlbrite/QueryObservable.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.sqlbrite; 2 | 3 | import android.database.Cursor; 4 | import android.support.annotation.CheckResult; 5 | import android.support.annotation.NonNull; 6 | import com.activeandroid.sqlbrite.SqlBrite.Query; 7 | import java.util.List; 8 | import rx.Observable; 9 | import rx.Subscriber; 10 | import rx.functions.Func1; 11 | 12 | /** An {@link Observable} of {@link Query} which offers query-specific convenience operators. */ 13 | public final class QueryObservable extends Observable { 14 | QueryObservable(final Observable o) { 15 | super(new OnSubscribe() { 16 | @Override public void call(Subscriber subscriber) { 17 | o.unsafeSubscribe(subscriber); 18 | } 19 | }); 20 | } 21 | 22 | /** 23 | * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each 24 | * emitted {@link Query} which returns a single row to {@code T}. 25 | *

26 | * It is an error for a query to pass through this operator with more than 1 row in its result 27 | * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows 28 | * do not emit an item. 29 | *

30 | * This method is equivalent to: 31 | *

{@code
32 |    * flatMap(q -> q.asRows(mapper).take(1))
33 |    * }
34 | * 35 | * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null. 36 | */ 37 | @CheckResult @NonNull 38 | public final Observable mapToOne(@NonNull final Func1 mapper) { 39 | return lift(new QueryToOneOperator<>(mapper, false, null)); 40 | } 41 | 42 | /** 43 | * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each 44 | * emitted {@link Query} which returns a single row to {@code T}. 45 | *

46 | * It is an error for a query to pass through this operator with more than 1 row in its result 47 | * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows 48 | * emit {@code defaultValue}. 49 | *

50 | * This method is equivalent to: 51 | *

{@code
52 |    * flatMap(q -> q.asRows(mapper).take(1).defaultIfEmpty(defaultValue))
53 |    * }
54 | * 55 | * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null. 56 | * @param defaultValue Value returned if result set is empty 57 | */ 58 | @CheckResult @NonNull 59 | public final Observable mapToOneOrDefault(@NonNull final Func1 mapper, 60 | T defaultValue) { 61 | return lift(new QueryToOneOperator<>(mapper, true, defaultValue)); 62 | } 63 | 64 | /** 65 | * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each 66 | * emitted {@link Query} to a {@code List}. 67 | *

68 | * Be careful using this operator as it will always consume the entire cursor and create objects 69 | * for each row, every time this observable emits a new query. On tables whose queries update 70 | * frequently or very large result sets this can result in the creation of many objects. 71 | *

72 | * This method is equivalent to: 73 | *

{@code
74 |    * flatMap(q -> q.asRows(mapper).toList())
75 |    * }
76 | * Consider using {@link Query#asRows} if you need to limit or filter in memory. 77 | * 78 | * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null. 79 | */ 80 | @CheckResult @NonNull 81 | public final Observable> mapToList(@NonNull final Func1 mapper) { 82 | return lift(new QueryToListOperator<>(mapper)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/com/activeandroid/sqlbrite/QueryToListOperator.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.sqlbrite; 2 | 3 | import android.database.Cursor; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import rx.Observable; 9 | import rx.Subscriber; 10 | import rx.exceptions.Exceptions; 11 | import rx.exceptions.OnErrorThrowable; 12 | import rx.functions.Func1; 13 | 14 | final class QueryToListOperator implements Observable.Operator, SqlBrite.Query> { 15 | private final Func1 mapper; 16 | 17 | QueryToListOperator(Func1 mapper) { 18 | this.mapper = mapper; 19 | } 20 | 21 | @Override 22 | public Subscriber call(final Subscriber> subscriber) { 23 | return new Subscriber(subscriber) { 24 | @Override public void onNext(SqlBrite.Query query) { 25 | try { 26 | Cursor cursor = query.run(); 27 | List items = new ArrayList<>(cursor.getCount()); 28 | try { 29 | for (int i = 1; cursor.moveToNext() && !subscriber.isUnsubscribed(); i++) { 30 | T item = mapper.call(cursor); 31 | if (item == null) { 32 | throw new NullPointerException("Mapper returned null for row " + i); 33 | } 34 | items.add(item); 35 | } 36 | } finally { 37 | cursor.close(); 38 | } 39 | if (!subscriber.isUnsubscribed()) { 40 | subscriber.onNext(items); 41 | } 42 | } catch (Throwable e) { 43 | Exceptions.throwIfFatal(e); 44 | onError(OnErrorThrowable.addValueAsLastCause(e, query.toString())); 45 | } 46 | } 47 | 48 | @Override public void onCompleted() { 49 | subscriber.onCompleted(); 50 | } 51 | 52 | @Override public void onError(Throwable e) { 53 | subscriber.onError(e); 54 | } 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/activeandroid/sqlbrite/QueryToOneOperator.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.sqlbrite; 2 | 3 | import android.database.Cursor; 4 | 5 | import rx.Observable; 6 | import rx.Subscriber; 7 | import rx.exceptions.Exceptions; 8 | import rx.exceptions.OnErrorThrowable; 9 | import rx.functions.Func1; 10 | 11 | final class QueryToOneOperator implements Observable.Operator { 12 | private final Func1 mapper; 13 | private boolean emitDefault; 14 | private T defaultValue; 15 | 16 | QueryToOneOperator(Func1 mapper, boolean emitDefault, T defaultValue) { 17 | this.mapper = mapper; 18 | this.emitDefault = emitDefault; 19 | this.defaultValue = defaultValue; 20 | } 21 | 22 | @Override public Subscriber call(final Subscriber subscriber) { 23 | return new Subscriber(subscriber) { 24 | @Override public void onNext(SqlBrite.Query query) { 25 | try { 26 | T item = null; 27 | Cursor cursor = query.run(); 28 | try { 29 | if (cursor.moveToNext()) { 30 | item = mapper.call(cursor); 31 | if (item == null) { 32 | throw new NullPointerException("Mapper returned null for row 1"); 33 | } 34 | if (cursor.moveToNext()) { 35 | throw new IllegalStateException("Cursor returned more than 1 row"); 36 | } 37 | } 38 | } finally { 39 | cursor.close(); 40 | } 41 | if (!subscriber.isUnsubscribed()) { 42 | if (item != null) { 43 | subscriber.onNext(item); 44 | } else if (emitDefault) { 45 | subscriber.onNext(defaultValue); 46 | } 47 | } 48 | } catch (Throwable e) { 49 | Exceptions.throwIfFatal(e); 50 | onError(OnErrorThrowable.addValueAsLastCause(e, query.toString())); 51 | } 52 | } 53 | 54 | @Override public void onCompleted() { 55 | subscriber.onCompleted(); 56 | } 57 | 58 | @Override public void onError(Throwable e) { 59 | subscriber.onError(e); 60 | } 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/com/activeandroid/sqlbrite/SqlBrite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.activeandroid.sqlbrite; 17 | 18 | import android.content.ContentResolver; 19 | import android.database.Cursor; 20 | import android.database.sqlite.SQLiteOpenHelper; 21 | import android.support.annotation.CheckResult; 22 | import android.support.annotation.NonNull; 23 | import android.util.Log; 24 | import rx.Observable; 25 | import rx.Subscriber; 26 | import rx.functions.Func1; 27 | 28 | /** 29 | * A lightweight wrapper around {@link SQLiteOpenHelper} which allows for continuously observing 30 | * the result of a query. 31 | */ 32 | public final class SqlBrite { 33 | @CheckResult @NonNull 34 | public static SqlBrite create() { 35 | return create(new Logger() { 36 | @Override public void log(String message) { 37 | Log.d("SqlBrite", message); 38 | } 39 | }); 40 | } 41 | 42 | @CheckResult @NonNull 43 | public static SqlBrite create(@NonNull Logger logger) { 44 | return new SqlBrite(logger); 45 | } 46 | 47 | private final Logger logger; 48 | 49 | private SqlBrite(@NonNull Logger logger) { 50 | this.logger = logger; 51 | } 52 | 53 | /** 54 | * Wrap a {@link SQLiteOpenHelper} for observable queries. 55 | *

56 | * While not strictly required, instances of this class assume that they will be the only ones 57 | * interacting with the underlying {@link SQLiteOpenHelper} and it is required for automatic 58 | * notifications of table changes to work. See {@linkplain BriteDatabase#createQuery the 59 | * query method} for more information on that behavior. 60 | */ 61 | @CheckResult @NonNull 62 | public BriteDatabase wrapDatabaseHelper(@NonNull SQLiteOpenHelper helper) { 63 | return new BriteDatabase(helper, logger); 64 | } 65 | 66 | /** Wrap a {@link ContentResolver} for observable queries. */ 67 | @CheckResult @NonNull 68 | public BriteContentResolver wrapContentProvider(@NonNull ContentResolver contentResolver) { 69 | return new BriteContentResolver(contentResolver, logger); 70 | } 71 | 72 | /** An executable query. */ 73 | public static abstract class Query { 74 | /** Execute the query on the underlying database and return the resulting cursor. */ 75 | @CheckResult // TODO @WorkerThread 76 | // TODO Implementations might return null, which is gross. Throw? 77 | public abstract Cursor run(); 78 | 79 | /** 80 | * Execute the query on the underlying database and return an Observable of each row mapped to 81 | * {@code T} by {@code mapper}. 82 | *

83 | * Standard usage of this operation is in {@code flatMap}: 84 | *

{@code
 85 |      * flatMap(q -> q.asRows(Item.MAPPER).toList())
 86 |      * }
87 | * However, the above is a more-verbose but identical operation as 88 | * {@link QueryObservable#mapToList}. This {@code asRows} method should be used when you need 89 | * to limit or filter the items separate from the actual query. 90 | *
{@code
 91 |      * flatMap(q -> q.asRows(Item.MAPPER).take(5).toList())
 92 |      * // or...
 93 |      * flatMap(q -> q.asRows(Item.MAPPER).filter(i -> i.isActive).toList())
 94 |      * }
95 | *

96 | * Note: Limiting results or filtering will almost always be faster in the database as part of 97 | * a query and should be preferred, where possible. 98 | */ 99 | @CheckResult @NonNull 100 | public final Observable asRows(final Func1 mapper) { 101 | return Observable.create(new Observable.OnSubscribe() { 102 | @Override public void call(Subscriber subscriber) { 103 | Cursor cursor = run(); 104 | try { 105 | while (cursor.moveToNext() && !subscriber.isUnsubscribed()) { 106 | subscriber.onNext(mapper.call(cursor)); 107 | } 108 | } finally { 109 | cursor.close(); 110 | } 111 | if (!subscriber.isUnsubscribed()) { 112 | subscriber.onCompleted(); 113 | } 114 | } 115 | }); 116 | } 117 | } 118 | 119 | /** A simple indirection for logging debug messages. */ 120 | public interface Logger { 121 | void log(String message); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/com/activeandroid/util/IOUtils.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.util; 3 | 4 | /* 5 | * Copyright (C) 2014 Markus Pfeiffer 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | import android.database.Cursor; 21 | 22 | import java.io.Closeable; 23 | import java.io.IOException; 24 | 25 | import com.activeandroid.util.Log; 26 | 27 | 28 | public class IOUtils { 29 | 30 | /** 31 | *

32 | * Unconditionally close a {@link Closeable}. 33 | *

34 | * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is 35 | * typically used in finally blocks. 36 | * @param closeable A {@link Closeable} to close. 37 | */ 38 | public static void closeQuietly(final Closeable closeable) { 39 | 40 | if (closeable == null) { 41 | return; 42 | } 43 | 44 | try { 45 | closeable.close(); 46 | } catch (final IOException e) { 47 | Log.e("Couldn't close closeable.", e); 48 | } 49 | } 50 | 51 | /** 52 | *

53 | * Unconditionally close a {@link Cursor}. 54 | *

55 | * Equivalent to {@link Cursor#close()}, except any exceptions will be ignored. This is 56 | * typically used in finally blocks. 57 | * @param cursor A {@link Cursor} to close. 58 | */ 59 | public static void closeQuietly(final Cursor cursor) { 60 | 61 | if (cursor == null) { 62 | return; 63 | } 64 | 65 | try { 66 | cursor.close(); 67 | } catch (final Exception e) { 68 | Log.e("Couldn't close cursor.", e); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/com/activeandroid/util/Log.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.util; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | public final class Log { 20 | ////////////////////////////////////////////////////////////////////////////////////// 21 | // PUBLIC MEMBERS 22 | ////////////////////////////////////////////////////////////////////////////////////// 23 | 24 | private static String sTag = "ActiveAndroid"; 25 | private static boolean sEnabled = false; 26 | 27 | ////////////////////////////////////////////////////////////////////////////////////// 28 | // CONSTRUCTORS 29 | ////////////////////////////////////////////////////////////////////////////////////// 30 | 31 | private Log() { 32 | } 33 | 34 | ////////////////////////////////////////////////////////////////////////////////////// 35 | // PUBLIC METHODS 36 | ////////////////////////////////////////////////////////////////////////////////////// 37 | 38 | public static boolean isEnabled() { 39 | return sEnabled; 40 | } 41 | 42 | public static void setEnabled(boolean enabled) { 43 | sEnabled = enabled; 44 | } 45 | 46 | public static boolean isLoggingEnabled() { 47 | return sEnabled; 48 | } 49 | 50 | public static int v(String msg) { 51 | if (sEnabled) { 52 | return android.util.Log.v(sTag, msg); 53 | } 54 | return 0; 55 | } 56 | 57 | public static int v(String tag, String msg) { 58 | if (sEnabled) { 59 | return android.util.Log.v(tag, msg); 60 | } 61 | return 0; 62 | } 63 | 64 | public static int v(String msg, Throwable tr) { 65 | if (sEnabled) { 66 | return android.util.Log.v(sTag, msg, tr); 67 | } 68 | return 0; 69 | } 70 | 71 | public static int v(String tag, String msg, Throwable tr) { 72 | if (sEnabled) { 73 | return android.util.Log.v(tag, msg, tr); 74 | } 75 | return 0; 76 | } 77 | 78 | public static int d(String msg) { 79 | if (sEnabled) { 80 | return android.util.Log.d(sTag, msg); 81 | } 82 | return 0; 83 | } 84 | 85 | public static int d(String tag, String msg) { 86 | if (sEnabled) { 87 | return android.util.Log.d(tag, msg); 88 | } 89 | return 0; 90 | } 91 | 92 | public static int d(String msg, Throwable tr) { 93 | if (sEnabled) { 94 | return android.util.Log.d(sTag, msg, tr); 95 | } 96 | return 0; 97 | } 98 | 99 | public static int d(String tag, String msg, Throwable tr) { 100 | if (sEnabled) { 101 | return android.util.Log.d(tag, msg, tr); 102 | } 103 | return 0; 104 | } 105 | 106 | public static int i(String msg) { 107 | if (sEnabled) { 108 | return android.util.Log.i(sTag, msg); 109 | } 110 | return 0; 111 | } 112 | 113 | public static int i(String tag, String msg) { 114 | if (sEnabled) { 115 | return android.util.Log.i(tag, msg); 116 | } 117 | return 0; 118 | } 119 | 120 | public static int i(String msg, Throwable tr) { 121 | if (sEnabled) { 122 | return android.util.Log.i(sTag, msg, tr); 123 | } 124 | return 0; 125 | } 126 | 127 | public static int i(String tag, String msg, Throwable tr) { 128 | if (sEnabled) { 129 | return android.util.Log.i(tag, msg, tr); 130 | } 131 | return 0; 132 | } 133 | 134 | public static int w(String msg) { 135 | if (sEnabled) { 136 | return android.util.Log.w(sTag, msg); 137 | } 138 | return 0; 139 | } 140 | 141 | public static int w(String tag, String msg) { 142 | if (sEnabled) { 143 | return android.util.Log.w(tag, msg); 144 | } 145 | return 0; 146 | } 147 | 148 | public static int w(String msg, Throwable tr) { 149 | if (sEnabled) { 150 | return android.util.Log.w(sTag, msg, tr); 151 | } 152 | return 0; 153 | } 154 | 155 | public static int w(String tag, String msg, Throwable tr) { 156 | if (sEnabled) { 157 | return android.util.Log.w(tag, msg, tr); 158 | } 159 | return 0; 160 | } 161 | 162 | public static int e(String msg) { 163 | if (sEnabled) { 164 | return android.util.Log.e(sTag, msg); 165 | } 166 | return 0; 167 | } 168 | 169 | public static int e(String tag, String msg) { 170 | if (sEnabled) { 171 | return android.util.Log.e(tag, msg); 172 | } 173 | return 0; 174 | } 175 | 176 | public static int e(String msg, Throwable tr) { 177 | if (sEnabled) { 178 | return android.util.Log.e(sTag, msg, tr); 179 | } 180 | return 0; 181 | } 182 | 183 | public static int e(String tag, String msg, Throwable tr) { 184 | if (sEnabled) { 185 | return android.util.Log.e(tag, msg, tr); 186 | } 187 | return 0; 188 | } 189 | 190 | public static int t(String msg, Object... args) { 191 | if (sEnabled) { 192 | return android.util.Log.v("test", String.format(msg, args)); 193 | } 194 | return 0; 195 | } 196 | } -------------------------------------------------------------------------------- /src/com/activeandroid/util/NaturalOrderComparator.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.util; 2 | 3 | /* 4 | NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java. 5 | Copyright (C) 2003 by Pierre-Luc Paour 6 | 7 | Based on the C version by Martin Pool, of which this is more or less a straight conversion. 8 | Copyright (C) 2000 by Martin Pool 9 | 10 | This software is provided 'as-is', without any express or implied 11 | warranty. In no event will the authors be held liable for any damages 12 | arising from the use of this software. 13 | 14 | Permission is granted to anyone to use this software for any purpose, 15 | including commercial applications, and to alter it and redistribute it 16 | freely, subject to the following restrictions: 17 | 18 | 1. The origin of this software must not be misrepresented; you must not 19 | claim that you wrote the original software. If you use this software 20 | in a product, an acknowledgment in the product documentation would be 21 | appreciated but is not required. 22 | 2. Altered source versions must be plainly marked as such, and must not be 23 | misrepresented as being the original software. 24 | 3. This notice may not be removed or altered from any source distribution. 25 | */ 26 | 27 | import java.util.Comparator; 28 | 29 | public class NaturalOrderComparator implements Comparator { 30 | int compareRight(String a, String b) { 31 | int bias = 0; 32 | int ia = 0; 33 | int ib = 0; 34 | 35 | // The longest run of digits wins. That aside, the greatest 36 | // value wins, but we can't know that it will until we've scanned 37 | // both numbers to know that they have the same magnitude, so we 38 | // remember it in BIAS. 39 | for (;; ia++, ib++) { 40 | char ca = charAt(a, ia); 41 | char cb = charAt(b, ib); 42 | 43 | if (!Character.isDigit(ca) && !Character.isDigit(cb)) { 44 | return bias; 45 | } 46 | else if (!Character.isDigit(ca)) { 47 | return -1; 48 | } 49 | else if (!Character.isDigit(cb)) { 50 | return +1; 51 | } 52 | else if (ca < cb) { 53 | if (bias == 0) { 54 | bias = -1; 55 | } 56 | } 57 | else if (ca > cb) { 58 | if (bias == 0) 59 | bias = +1; 60 | } 61 | else if (ca == 0 && cb == 0) { 62 | return bias; 63 | } 64 | } 65 | } 66 | 67 | public int compare(Object o1, Object o2) { 68 | String a = o1.toString(); 69 | String b = o2.toString(); 70 | 71 | int ia = 0, ib = 0; 72 | int nza = 0, nzb = 0; 73 | char ca, cb; 74 | int result; 75 | 76 | while (true) { 77 | // only count the number of zeroes leading the last number compared 78 | nza = nzb = 0; 79 | 80 | ca = charAt(a, ia); 81 | cb = charAt(b, ib); 82 | 83 | // skip over leading spaces or zeros 84 | while (Character.isSpaceChar(ca) || ca == '0') { 85 | if (ca == '0') { 86 | nza++; 87 | } 88 | else { 89 | // only count consecutive zeroes 90 | nza = 0; 91 | } 92 | 93 | ca = charAt(a, ++ia); 94 | } 95 | 96 | while (Character.isSpaceChar(cb) || cb == '0') { 97 | if (cb == '0') { 98 | nzb++; 99 | } 100 | else { 101 | // only count consecutive zeroes 102 | nzb = 0; 103 | } 104 | 105 | cb = charAt(b, ++ib); 106 | } 107 | 108 | // process run of digits 109 | if (Character.isDigit(ca) && Character.isDigit(cb)) { 110 | if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) { 111 | return result; 112 | } 113 | } 114 | 115 | if (ca == 0 && cb == 0) { 116 | // The strings compare the same. Perhaps the caller 117 | // will want to call strcmp to break the tie. 118 | return nza - nzb; 119 | } 120 | 121 | if (ca < cb) { 122 | return -1; 123 | } 124 | else if (ca > cb) { 125 | return +1; 126 | } 127 | 128 | ++ia; 129 | ++ib; 130 | } 131 | } 132 | 133 | static char charAt(String s, int i) { 134 | if (i >= s.length()) { 135 | return 0; 136 | } 137 | else { 138 | return s.charAt(i); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /src/com/activeandroid/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.util; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.lang.reflect.Field; 20 | import java.lang.reflect.Modifier; 21 | import java.util.Arrays; 22 | import java.util.Collections; 23 | import java.util.Comparator; 24 | import java.util.LinkedHashSet; 25 | import java.util.Set; 26 | 27 | import android.content.Context; 28 | import android.content.pm.ApplicationInfo; 29 | import android.content.pm.PackageManager; 30 | 31 | import com.activeandroid.Model; 32 | import com.activeandroid.annotation.Column; 33 | import com.activeandroid.serializer.TypeSerializer; 34 | 35 | public final class ReflectionUtils { 36 | ////////////////////////////////////////////////////////////////////////////////////// 37 | // PUBLIC METHODS 38 | ////////////////////////////////////////////////////////////////////////////////////// 39 | 40 | public static boolean isModel(Class type) { 41 | return isSubclassOf(type, Model.class) && (!Modifier.isAbstract(type.getModifiers())); 42 | } 43 | 44 | public static boolean isTypeSerializer(Class type) { 45 | return isSubclassOf(type, TypeSerializer.class); 46 | } 47 | 48 | // Meta-data 49 | 50 | @SuppressWarnings("unchecked") 51 | public static T getMetaData(Context context, String name) { 52 | try { 53 | final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), 54 | PackageManager.GET_META_DATA); 55 | 56 | if (ai.metaData != null) { 57 | return (T) ai.metaData.get(name); 58 | } 59 | } 60 | catch (Exception e) { 61 | Log.w("Couldn't find meta-data: " + name); 62 | } 63 | 64 | return null; 65 | } 66 | 67 | public static Set getDeclaredColumnFields(Class type) { 68 | Set declaredColumnFields = Collections.emptySet(); 69 | 70 | if (ReflectionUtils.isSubclassOf(type, Model.class) || Model.class.equals(type)) { 71 | declaredColumnFields = new LinkedHashSet(); 72 | 73 | Field[] fields = type.getDeclaredFields(); 74 | Arrays.sort(fields, new Comparator() { 75 | @Override 76 | public int compare(Field field1, Field field2) { 77 | return field2.getName().compareTo(field1.getName()); 78 | } 79 | }); 80 | for (Field field : fields) { 81 | if (field.isAnnotationPresent(Column.class)) { 82 | declaredColumnFields.add(field); 83 | } 84 | } 85 | 86 | Class parentType = type.getSuperclass(); 87 | if (parentType != null) { 88 | declaredColumnFields.addAll(getDeclaredColumnFields(parentType)); 89 | } 90 | } 91 | 92 | return declaredColumnFields; 93 | } 94 | 95 | ////////////////////////////////////////////////////////////////////////////////////// 96 | // PRIVATE METHODS 97 | ////////////////////////////////////////////////////////////////////////////////////// 98 | 99 | public static boolean isSubclassOf(Class type, Class superClass) { 100 | if (type.getSuperclass() != null) { 101 | if (type.getSuperclass().equals(superClass)) { 102 | return true; 103 | } 104 | 105 | return isSubclassOf(type.getSuperclass(), superClass); 106 | } 107 | 108 | return false; 109 | } 110 | } -------------------------------------------------------------------------------- /src/com/activeandroid/util/SqlParser.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.util; 3 | 4 | /* 5 | * Copyright (C) 2014 Markus Pfeiffer 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | import java.io.BufferedInputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | 27 | public class SqlParser { 28 | 29 | public final static int STATE_NONE = 0; 30 | public final static int STATE_STRING = 1; 31 | public final static int STATE_COMMENT = 2; 32 | public final static int STATE_COMMENT_BLOCK = 3; 33 | 34 | public static List parse(final InputStream stream) throws IOException { 35 | 36 | final BufferedInputStream buffer = new BufferedInputStream(stream); 37 | final List commands = new ArrayList(); 38 | final StringBuffer sb = new StringBuffer(); 39 | 40 | try { 41 | final Tokenizer tokenizer = new Tokenizer(buffer); 42 | int state = STATE_NONE; 43 | 44 | while (tokenizer.hasNext()) { 45 | final char c = (char) tokenizer.next(); 46 | 47 | if (state == STATE_COMMENT_BLOCK) { 48 | if (tokenizer.skip("*/")) { 49 | state = STATE_NONE; 50 | } 51 | continue; 52 | 53 | } else if (state == STATE_COMMENT) { 54 | if (isNewLine(c)) { 55 | state = STATE_NONE; 56 | } 57 | continue; 58 | 59 | } else if (state == STATE_NONE && tokenizer.skip("/*")) { 60 | state = STATE_COMMENT_BLOCK; 61 | continue; 62 | 63 | } else if (state == STATE_NONE && tokenizer.skip("--")) { 64 | state = STATE_COMMENT; 65 | continue; 66 | 67 | } else if (state == STATE_NONE && c == ';') { 68 | final String command = sb.toString().trim(); 69 | commands.add(command); 70 | sb.setLength(0); 71 | continue; 72 | 73 | } else if (state == STATE_NONE && c == '\'') { 74 | state = STATE_STRING; 75 | 76 | } else if (state == STATE_STRING && c == '\'') { 77 | state = STATE_NONE; 78 | 79 | } 80 | 81 | if (state == STATE_NONE || state == STATE_STRING) { 82 | if (state == STATE_NONE && isWhitespace(c)) { 83 | if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') { 84 | sb.append(' '); 85 | } 86 | } else { 87 | sb.append(c); 88 | } 89 | } 90 | } 91 | 92 | } finally { 93 | IOUtils.closeQuietly(buffer); 94 | } 95 | 96 | if (sb.length() > 0) { 97 | commands.add(sb.toString().trim()); 98 | } 99 | 100 | return commands; 101 | } 102 | 103 | private static boolean isNewLine(final char c) { 104 | return c == '\r' || c == '\n'; 105 | } 106 | 107 | private static boolean isWhitespace(final char c) { 108 | return c == '\r' || c == '\n' || c == '\t' || c == ' '; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/com/activeandroid/util/Tokenizer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.util; 3 | 4 | /* 5 | * Copyright (C) 2014 Markus Pfeiffer 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | 23 | 24 | public class Tokenizer { 25 | 26 | private final InputStream mStream; 27 | 28 | private boolean mIsNext; 29 | private int mCurrent; 30 | 31 | public Tokenizer(final InputStream in) { 32 | this.mStream = in; 33 | } 34 | 35 | public boolean hasNext() throws IOException { 36 | 37 | if (!this.mIsNext) { 38 | this.mIsNext = true; 39 | this.mCurrent = this.mStream.read(); 40 | } 41 | return this.mCurrent != -1; 42 | } 43 | 44 | public int next() throws IOException { 45 | 46 | if (!this.mIsNext) { 47 | this.mCurrent = this.mStream.read(); 48 | } 49 | this.mIsNext = false; 50 | return this.mCurrent; 51 | } 52 | 53 | public boolean skip(final String s) throws IOException { 54 | 55 | if (s == null || s.length() == 0) { 56 | return false; 57 | } 58 | 59 | if (s.charAt(0) != this.mCurrent) { 60 | return false; 61 | } 62 | 63 | final int len = s.length(); 64 | this.mStream.mark(len - 1); 65 | 66 | for (int n = 1; n < len; n++) { 67 | final int value = this.mStream.read(); 68 | 69 | if (value != s.charAt(n)) { 70 | this.mStream.reset(); 71 | return false; 72 | } 73 | } 74 | return true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/com/activeandroid/widget/ModelAdapter.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.widget; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | import android.content.Context; 7 | import android.widget.ArrayAdapter; 8 | 9 | import com.activeandroid.Model; 10 | 11 | public class ModelAdapter extends ArrayAdapter { 12 | public ModelAdapter(Context context, int textViewResourceId) { 13 | super(context, textViewResourceId); 14 | } 15 | 16 | public ModelAdapter(Context context, int resource, int textViewResourceId) { 17 | super(context, resource, textViewResourceId); 18 | } 19 | 20 | public ModelAdapter(Context context, int textViewResourceId, List objects) { 21 | super(context, textViewResourceId, objects); 22 | } 23 | 24 | public ModelAdapter(Context context, int resource, int textViewResourceId, List objects) { 25 | super(context, resource, textViewResourceId, objects); 26 | } 27 | 28 | /** 29 | * Clears the adapter and, if data != null, fills if with new Items. 30 | * 31 | * @param collection A Collection<? extends T> which members get added to the adapter. 32 | */ 33 | public void setData(Collection collection) { 34 | clear(); 35 | 36 | if (collection != null) { 37 | for (T item : collection) { 38 | add(item); 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * @return The Id of the record at position. 45 | */ 46 | @Override 47 | public long getItemId(int position) { 48 | T item = getItem(position); 49 | 50 | if (item != null) { 51 | return item.getId(); 52 | } 53 | else { 54 | return -1; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | lint.xml 5 | 6 | #Eclipse 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | 12 | #IntelliJ IDEA 13 | .idea 14 | *.iml 15 | *.ipr 16 | *.iws 17 | classes 18 | gen-external-apklibs 19 | 20 | #Maven 21 | target 22 | release.properties 23 | pom.xml.* 24 | 25 | #Ant 26 | build.xml 27 | ant.properties 28 | local.properties 29 | proguard.cfg 30 | proguard-project.txt 31 | 32 | #Other 33 | .DS_Store 34 | tmp 35 | -------------------------------------------------------------------------------- /tests/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/assets/migrations/2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS MockMigration 2 | ( 3 | Id INTEGER AUTO_INCREMENT PRIMARY KEY, 4 | Column TEXT NOT NULL 5 | ); 6 | 7 | INSERT INTO MockMigration 8 | ( 9 | Id, 10 | Column 11 | ) 12 | VALUES 13 | ( 14 | 1, 15 | 'text' 16 | ); 17 | 18 | DROP TABLE IF EXISTS MockMigration; 19 | -------------------------------------------------------------------------------- /tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 4.0.0 8 | 9 | 10 | com.activeandroid 11 | activeandroid-parent 12 | 3.1-SNAPSHOT 13 | 14 | 15 | com.activeandroid 16 | activeandroid-tests 17 | 3.1-SNAPSHOT 18 | apk 19 | ActiveAndroid - Tests 20 | 21 | 22 | 1.6 23 | 16 24 | 4.1.1.4 25 | r7 26 | 27 | 28 | 29 | 30 | com.google.android 31 | android-test 32 | ${platform.version} 33 | provided 34 | 35 | 36 | com.activeandroid 37 | activeandroid 38 | jar 39 | 3.1-SNAPSHOT 40 | 41 | 42 | junit 43 | junit 44 | 4.10 45 | provided 46 | 47 | 48 | 49 | src 50 | 51 | 52 | com.jayway.maven.plugins.android.generation2 53 | android-maven-plugin 54 | 3.8.2 55 | 56 | ${project.basedir}/AndroidManifest.xml 57 | ${project.basedir}/assets 58 | ${project.basedir}/res 59 | 60 | ${platform.sdk} 61 | 62 | true 63 | 64 | true 65 | 66 | 67 | true 68 | 69 | 70 | maven-compiler-plugin 71 | 3.1 72 | 73 | ${java.version} 74 | ${java.version} 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /tests/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /tests/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/tests/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /tests/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/tests/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /tests/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/tests/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /tests/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicpinm/ActiveAndroidRx/47a8fddad728f6ccb8f4a3587137fe6648c7abd9/tests/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /tests/res/raw/block_comment.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | Column1 INTEGER /* This is a block comment and should be ignored */ 5 | ) 6 | -------------------------------------------------------------------------------- /tests/res/raw/block_comment_with_semicolon.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, /* block comment ; with semicolon */ 4 | Column1 INTEGER 5 | ) 6 | -------------------------------------------------------------------------------- /tests/res/raw/block_comment_with_string.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | Column1 INTEGER /* This is a block comment 'with a string that doesn't matter' */ 5 | ) 6 | -------------------------------------------------------------------------------- /tests/res/raw/block_with_line_comment.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, /* This is a block comment -- not a line comment */ Column1 INTEGER 4 | ) 5 | -------------------------------------------------------------------------------- /tests/res/raw/complex.sql: -------------------------------------------------------------------------------- 1 | -- Create table for migration 2 | CREATE TABLE Entity2 3 | ( 4 | Id INTEGER AUTO_INCREMENT PRIMARY KEY, 5 | Column TEXT NOT NULL, 6 | Column2 INTEGER NULL /* this column is new */ 7 | ); 8 | 9 | -- Migrate data 10 | INSERT INTO Entity2 11 | ( 12 | Id, 13 | Column, /* --> ; <-- */ 14 | Column2 15 | ) 16 | SELECT Id, 17 | Column, 18 | 0 -- there's no such value in the old table 19 | FROM Entity; 20 | 21 | -- Rename Entity2 to Entity 22 | DROP TABLE Entity; 23 | ALTER TABLE Entity2 RENAME TO Entity; 24 | 25 | /* Add some --sample-- data */ 26 | INSERT INTO Entity2 27 | ( 28 | Id, --;'/*;*/-- 29 | Col/*not sure if anyone would ever be insane enough to do this*/umn, 30 | Column2--, 31 | ) 32 | VALUES 33 | ( 34 | 9001 /* not -- really */, -- almost forgot that comma 35 | 42,--23, /* I don't know who messed this up 36 | 'string /* string */ -- string'--, 37 | -- 'test' whoops we don't have that many columns 38 | ) -------------------------------------------------------------------------------- /tests/res/raw/invalid_block_comment.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | /* /* /* This is an invalid block comment */ */ 5 | Column1 INTEGER 6 | ) 7 | -------------------------------------------------------------------------------- /tests/res/raw/line_comment.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | Column1 INTEGER -- This is a line comment and should be ignored 5 | ) 6 | -------------------------------------------------------------------------------- /tests/res/raw/line_comment_and_block_end.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | -- This is a line comment and should be ignored */ NonColumn STRING, 5 | Column1 INTEGER 6 | ) 7 | -------------------------------------------------------------------------------- /tests/res/raw/line_comment_with_semicolon.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, -- line comment ; with semicolon 4 | Column1 INTEGER 5 | ) 6 | -------------------------------------------------------------------------------- /tests/res/raw/line_comment_with_string.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | Column1 INTEGER -- This is a line comment 'with a string that doesn't matter' 5 | ) 6 | -------------------------------------------------------------------------------- /tests/res/raw/string_with_block_comment.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO Entity 2 | ( 3 | Id, 4 | Column1, 5 | Column2 6 | ) 7 | VALUES 8 | ( 9 | 1, 10 | '/* some text', 11 | 'some text */' 12 | ); -------------------------------------------------------------------------------- /tests/res/raw/string_with_line_comment.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO Entity 2 | ( 3 | Id, 4 | Column1, 5 | Column2 6 | ) 7 | VALUES 8 | ( 9 | 1, 10 | '-- some text', 11 | 'some text' 12 | ); -------------------------------------------------------------------------------- /tests/res/raw/string_with_semicolon.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO Entity 2 | ( 3 | Id, 4 | Column1, 5 | Column2 6 | ) 7 | VALUES 8 | ( 9 | 1, 10 | 'some ; text', 11 | 'some ; text' 12 | ); -------------------------------------------------------------------------------- /tests/res/raw/string_with_whitespace.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO Entity 2 | ( 3 | Id, 4 | Column1, 5 | Column2 6 | ) 7 | VALUES 8 | ( 9 | 1, 10 | 'some text', 11 | 'some text' 12 | ); -------------------------------------------------------------------------------- /tests/res/raw/two_statements.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | ( 3 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 4 | Column1 INTEGER 5 | ); 6 | 7 | CREATE TABLE Entity2 8 | ( 9 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 10 | Column1 INTEGER 11 | ) 12 | -------------------------------------------------------------------------------- /tests/res/raw/whitespace.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Entity1 2 | 3 | 4 | ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Column1 INTEGER 36 | ); -------------------------------------------------------------------------------- /tests/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestsTest 5 | 6 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/ActiveAndroidTestCase.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.test.ApplicationTestCase; 20 | 21 | import com.activeandroid.app.Application; 22 | 23 | public abstract class ActiveAndroidTestCase extends ApplicationTestCase { 24 | public ActiveAndroidTestCase() { 25 | super(Application.class); 26 | } 27 | 28 | protected void setUp() throws Exception { 29 | super.setUp(); 30 | createApplication(); 31 | } 32 | 33 | protected void tearDown() throws Exception { 34 | super.tearDown(); 35 | } 36 | 37 | public static void assertArrayEquals(T[] actual, T... expected) { 38 | assertEquals(expected.length, actual.length); 39 | 40 | for (int i = 0; i < expected.length; i++) { 41 | assertEquals(expected[i], actual[i]); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/CacheTest.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test; 2 | 3 | import android.test.AndroidTestCase; 4 | 5 | import com.activeandroid.ActiveAndroid; 6 | import com.activeandroid.Cache; 7 | import com.activeandroid.Configuration; 8 | import com.activeandroid.Model; 9 | import com.activeandroid.TableInfo; 10 | import com.activeandroid.annotation.Table; 11 | 12 | import java.util.Collection; 13 | 14 | public class CacheTest extends AndroidTestCase { 15 | 16 | @Override 17 | protected void setUp() { 18 | Configuration conf = new Configuration.Builder(getContext()) 19 | .setDatabaseName("CacheTest") 20 | .addModelClasses(CacheTestModel.class, CacheTestModel2.class) 21 | .create(); 22 | ActiveAndroid.initialize(conf, true); 23 | } 24 | 25 | public void testGetTableInfos() { 26 | assertNotNull(Cache.getContext()); 27 | Collection tableInfos = Cache.getTableInfos(); 28 | assertEquals(2, tableInfos.size()); 29 | 30 | { 31 | TableInfo tableInfo = Cache.getTableInfo(CacheTestModel.class); 32 | assertNotNull(tableInfo); 33 | assertEquals("CacheTestModel", tableInfo.getTableName()); 34 | } 35 | 36 | { 37 | TableInfo tableInfo = Cache.getTableInfo(CacheTestModel2.class); 38 | assertNotNull(tableInfo); 39 | assertEquals("CacheTestModel2", tableInfo.getTableName()); 40 | } 41 | } 42 | 43 | @Table(name = "CacheTestModel") 44 | private static class CacheTestModel extends Model { 45 | } 46 | 47 | @Table(name = "CacheTestModel2") 48 | private static class CacheTestModel2 extends Model { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/ConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test; 2 | 3 | import com.activeandroid.Configuration; 4 | import com.activeandroid.Model; 5 | import com.activeandroid.annotation.Table; 6 | 7 | import android.test.AndroidTestCase; 8 | 9 | import java.io.IOException; 10 | import java.util.List; 11 | 12 | public class ConfigurationTest extends AndroidTestCase { 13 | 14 | public void testDefaultValue() throws IOException, ClassNotFoundException { 15 | Configuration conf = new Configuration.Builder(getContext()).create(); 16 | assertNotNull(conf.getContext()); 17 | assertEquals(1024, conf.getCacheSize()); 18 | assertEquals("Application.db", conf.getDatabaseName()); 19 | assertEquals(1, conf.getDatabaseVersion()); 20 | assertNull(conf.getModelClasses()); 21 | assertFalse(conf.isValid()); 22 | assertNull(conf.getTypeSerializers()); 23 | assertEquals(Configuration.SQL_PARSER_LEGACY, conf.getSqlParser()); 24 | } 25 | 26 | public void testCreateConfigurationWithMockModel() { 27 | Configuration conf = new Configuration.Builder(getContext()) 28 | .addModelClass(ConfigurationTestModel.class) 29 | .create(); 30 | List> modelClasses = conf.getModelClasses(); 31 | assertEquals(1, modelClasses.size()); 32 | assertTrue(conf.isValid()); 33 | } 34 | 35 | @Table(name = "ConfigurationTestModel") 36 | private static class ConfigurationTestModel extends Model { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/MockModel.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.Model; 20 | import com.activeandroid.annotation.Column; 21 | import com.activeandroid.annotation.Table; 22 | 23 | import java.util.Date; 24 | 25 | @Table(name = "MockModel") 26 | public class MockModel extends Model { 27 | @Column 28 | public Date dateField; 29 | 30 | @Column 31 | public double doubleField; 32 | 33 | @Column 34 | public int intField; 35 | 36 | @Column 37 | public boolean booleanField; 38 | } 39 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/ModelTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Vojtech Sigler. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.activeandroid.test; 18 | 19 | import com.activeandroid.Cache; 20 | import com.activeandroid.Model; 21 | import com.activeandroid.TableInfo; 22 | import com.activeandroid.annotation.Column; 23 | import com.activeandroid.annotation.Table; 24 | import com.activeandroid.query.Select; 25 | 26 | import java.lang.reflect.Field; 27 | import java.util.ArrayList; 28 | import java.util.Date; 29 | import java.util.HashSet; 30 | import java.util.List; 31 | import java.util.Set; 32 | 33 | /** 34 | * Simple test now covering equals and hashcode methods. 35 | */ 36 | public class ModelTest extends ActiveAndroidTestCase { 37 | 38 | /** 39 | * Equals should be type-safe. 40 | */ 41 | public void testEqualsNonModel() { 42 | MockModel model = new MockModel(); 43 | 44 | assertFalse(model.equals("Dummy")); 45 | assertFalse(model.equals(null)); 46 | } 47 | 48 | /** 49 | * Equals should not be true for different model classes. 50 | */ 51 | public void testEqualsDifferentModel() { 52 | Model model1 = new MockModel(); 53 | Model model2 = new AnotherMockModel(); 54 | 55 | assertFalse(model1.equals(model2)); 56 | } 57 | 58 | /** 59 | * A new object does not have PK assigned yet, 60 | * therefore by default it is equal only to itself. 61 | */ 62 | public void testEqualsOnNew() { 63 | MockModel model1 = new MockModel(); 64 | MockModel model2 = new MockModel(); 65 | 66 | assertFalse(model1.equals(model2)); 67 | assertFalse(model2.equals(model1)); 68 | assertTrue(model1.equals(model1)); //equal only to itself 69 | } 70 | 71 | /** 72 | * Two different rows in a table should not be equal (different ids). 73 | */ 74 | public void testEqualsDifferentRows() { 75 | MockModel model1 = new MockModel(); 76 | MockModel model2 = new MockModel(); 77 | MockModel model3; 78 | 79 | model1.save(); 80 | model2.save(); 81 | model3 = Model.load(MockModel.class, model1.getId()); 82 | 83 | // Not equal to each other. 84 | assertFalse(model1.equals(model2)); 85 | assertFalse(model2.equals(model1)); 86 | 87 | // Equal to each other when loaded. 88 | assertTrue(model1.equals(model3)); 89 | assertTrue(model1.equals(model3)); 90 | 91 | // Loaded model is not equal to a different model. 92 | assertFalse(model3.equals(model2)); 93 | assertFalse(model2.equals(model3)); 94 | } 95 | 96 | /** 97 | * Tests hashcode for new instances. 98 | */ 99 | public void testHashCode() { 100 | Set set = new HashSet(); 101 | Model m1 = new MockModel(); 102 | Model m2 = new MockModel(); 103 | Model m3 = new AnotherMockModel(); 104 | 105 | assertFalse(m1.hashCode() == m2.hashCode()); // hashes for unsaved models must not match 106 | set.add(m1); 107 | set.add(m2); 108 | assertEquals(2, set.size()); //try in a set 109 | 110 | assertFalse(m1.hashCode() == m3.hashCode()); 111 | set.add(m3); 112 | assertEquals(3, set.size()); 113 | } 114 | 115 | /** 116 | * Two rows in a table should have different hashcodes. 117 | */ 118 | public void testHashCodeDifferentRows() { 119 | Set set = new HashSet(); 120 | Model m1 = new MockModel(); 121 | Model m2 = new MockModel(); 122 | Model m3; 123 | 124 | m1.save(); 125 | m2.save(); 126 | m3 = Model.load(MockModel.class, m1.getId()); 127 | 128 | assertEquals(m1.hashCode(), m3.hashCode()); 129 | assertFalse(m1.hashCode() == m2.hashCode()); 130 | set.add(m1); 131 | set.add(m2); 132 | set.add(m3); 133 | assertEquals(2, set.size()); 134 | } 135 | 136 | /** 137 | * Column names should default to the field name. 138 | */ 139 | public void testColumnNamesDefaulToFieldNames() { 140 | TableInfo tableInfo = Cache.getTableInfo(MockModel.class); 141 | 142 | for ( Field field : tableInfo.getFields() ) { 143 | // Id column is a special case, we'll ignore that one. 144 | if ( field.getName().equals("mId") ) continue; 145 | 146 | assertEquals(field.getName(), tableInfo.getColumnName(field)); 147 | } 148 | } 149 | 150 | /** 151 | * Boolean should handle integer (0/1) and boolean (false/true) values. 152 | */ 153 | public void testBooleanColumnType() { 154 | MockModel mockModel = new MockModel(); 155 | mockModel.booleanField = false; 156 | Long id = mockModel.save(); 157 | 158 | boolean databaseBooleanValue = MockModel.load( MockModel.class, id ).booleanField; 159 | 160 | assertEquals( false, databaseBooleanValue ); 161 | 162 | // Test passing both a integer and a boolean into the where conditional. 163 | assertEquals( 164 | mockModel, 165 | new Select().from(MockModel.class).where("booleanField = ?", 0).executeSingle() ); 166 | 167 | assertEquals( 168 | mockModel, 169 | new Select().from(MockModel.class).where("booleanField = ?", false).executeSingle() ); 170 | 171 | assertNull( new Select().from(MockModel.class).where("booleanField = ?", 1).executeSingle() ); 172 | 173 | assertNull( new Select().from(MockModel.class).where("booleanField = ?", true).executeSingle() ); 174 | } 175 | 176 | /** 177 | * Test to check the join of two (or more) tables with some fields in common when not use a projection on select. 178 | * Test the issue #106 (https://github.com/pardom/ActiveAndroid/issues/106) 179 | */ 180 | public void testJoinWithSameNames(){ 181 | //create a parent entity and store 182 | ParentJoinMockModel parent = new ParentJoinMockModel(); 183 | parent.booleanField = true; 184 | parent.dateField = new Date(); 185 | parent.doubleField = 2.0; 186 | parent.intField = 1; 187 | parent.save(); 188 | 189 | //the values to assign to child 190 | Date dateValue = new Date(); 191 | double doubleValue = 30.0; 192 | int intValue = 3; 193 | 194 | //create two child entities, relate with parent and save 195 | ChildMockModel child1 = new ChildMockModel(); 196 | child1.booleanField = false; 197 | child1.dateField = dateValue; 198 | child1.doubleField = doubleValue; 199 | child1.intField = intValue; 200 | child1.parent = parent; 201 | child1.save(); 202 | 203 | ChildMockModel child2 = new ChildMockModel(); 204 | child2.booleanField = false; 205 | child2.dateField = dateValue; 206 | child2.doubleField = doubleValue; 207 | child2.intField = intValue; 208 | child2.parent = parent; 209 | child2.save(); 210 | 211 | //Store the ids assigned to child entities when persists 212 | List ids = new ArrayList(); 213 | ids.add(child1.getId()); 214 | ids.add(child2.getId()); 215 | 216 | //make the query with a join 217 | List result = new Select().from(ChildMockModel.class). 218 | join(ParentJoinMockModel.class).on("ParentJoinMockModel.Id = ChildMockModel.parent").execute(); 219 | 220 | //check result 221 | assertNotNull(result); 222 | assertEquals(result.size(), 2); 223 | for(ChildMockModel currentModel : result){ 224 | assertFalse(currentModel.booleanField); 225 | assertEquals(currentModel.intField, intValue); 226 | assertEquals(currentModel.doubleField, doubleValue); 227 | assertTrue(ids.contains(currentModel.getId())); 228 | } 229 | 230 | } 231 | 232 | /** 233 | * Mock model as we need 2 different model classes. 234 | */ 235 | @Table(name = "AnotherMockTable") 236 | public static class AnotherMockModel extends Model {} 237 | 238 | /** 239 | * Mock model to test joins with same names. 240 | * It's a copy from MockModel. 241 | */ 242 | @Table(name = "ParentJoinMockModel") 243 | public static class ParentJoinMockModel extends Model { 244 | @Column 245 | public Date dateField; 246 | 247 | @Column 248 | public double doubleField; 249 | 250 | @Column 251 | public int intField; 252 | 253 | @Column 254 | public boolean booleanField; 255 | } 256 | 257 | /** 258 | * Mock model to test joins with same names. 259 | * Extends from ParentJoinMockModel to have the same columns. 260 | * Have a relationship with ParentJoinMockModel to make te join query. 261 | */ 262 | @Table(name = "ChildMockModel") 263 | public static class ChildMockModel extends ParentJoinMockModel { 264 | @Column 265 | ParentJoinMockModel parent; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/parser/ParserConfigurationTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.test.parser; 3 | 4 | import android.database.SQLException; 5 | import android.database.sqlite.SQLiteDatabase; 6 | 7 | import com.activeandroid.Configuration; 8 | import com.activeandroid.DatabaseHelper; 9 | import com.activeandroid.test.ActiveAndroidTestCase; 10 | 11 | 12 | public class ParserConfigurationTest extends ActiveAndroidTestCase { 13 | 14 | /** 15 | * Should try to use the legacy parser by default, which is be unable to handle the SQL script. 16 | */ 17 | public void testLegacyMigration() { 18 | 19 | try { 20 | Configuration configuration = new Configuration.Builder(getContext()) 21 | .setDatabaseName("migration.db") 22 | .setDatabaseVersion(2) 23 | .create(); 24 | 25 | DatabaseHelper helper = new DatabaseHelper(configuration); 26 | SQLiteDatabase db = helper.getWritableDatabase(); 27 | helper.onUpgrade(db, 1, 2); 28 | 29 | fail("Should not be able to parse the SQL script."); 30 | 31 | } catch (SQLException e) { 32 | final String message = e.getMessage(); 33 | 34 | assertNotNull(message); 35 | assertTrue(message.contains("syntax error")); 36 | assertTrue(message.contains("near \"MockMigration\"")); 37 | } 38 | } 39 | 40 | /** 41 | * Should use the new parser if configured to do so. 42 | */ 43 | public void testDelimitedMigration() { 44 | Configuration configuration = new Configuration.Builder(getContext()) 45 | .setSqlParser(Configuration.SQL_PARSER_DELIMITED) 46 | .setDatabaseName("migration.db") 47 | .setDatabaseVersion(2) 48 | .create(); 49 | 50 | DatabaseHelper helper = new DatabaseHelper(configuration); 51 | SQLiteDatabase db = helper.getWritableDatabase(); 52 | helper.onUpgrade(db, 1, 2); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/parser/ParserTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.test.parser; 3 | 4 | import com.activeandroid.test.ActiveAndroidTestCase; 5 | import com.activeandroid.test.R; 6 | import com.activeandroid.util.SqlParser; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.List; 11 | 12 | 13 | public class ParserTest extends ActiveAndroidTestCase { 14 | 15 | private final String sql1 = "CREATE TABLE Entity1 ( Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, Column1 INTEGER )"; 16 | private final String sql2 = "CREATE TABLE Entity2 ( Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, Column1 INTEGER )"; 17 | 18 | private final String invalid = "CREATE TABLE Entity1 ( Id INTEGER AUTOINCREMENT PRIMARY KEY NOT NULL, */ Column1 INTEGER )"; 19 | 20 | private InputStream getStream(int id) { 21 | return this.getContext().getResources().openRawResource(id); 22 | } 23 | 24 | /** 25 | * Should be able to parse a script with two multi-line statments, even if the last statement 26 | * is not terminated by a semicolon. 27 | * @throws IOException 28 | */ 29 | public void testTwoStatements() throws IOException { 30 | 31 | final InputStream stream = this.getStream(R.raw.two_statements); 32 | List commands = SqlParser.parse(stream); 33 | 34 | assertEquals(2, commands.size()); 35 | assertEquals(sql1, commands.get(0)); 36 | assertEquals(sql2, commands.get(1)); 37 | } 38 | 39 | /** 40 | * Should reduce unnecessary whitespace. 41 | * @throws IOException 42 | */ 43 | public void testWhitespace() throws IOException { 44 | 45 | final InputStream stream = this.getStream(R.raw.whitespace); 46 | List commands = SqlParser.parse(stream); 47 | 48 | assertEquals(1, commands.size()); 49 | assertEquals(sql1, commands.get(0)); 50 | } 51 | 52 | /** 53 | * Should be able to parse a multi-line statement that has an embedded line comment. 54 | * @throws IOException 55 | */ 56 | public void testLineComment() throws IOException { 57 | 58 | final InputStream stream = this.getStream(R.raw.line_comment); 59 | List commands = SqlParser.parse(stream); 60 | 61 | assertEquals(1, commands.size()); 62 | assertEquals(sql1, commands.get(0)); 63 | } 64 | 65 | /** 66 | * Should be able to handle a line comment that contains string tokens. 67 | * @throws IOException 68 | */ 69 | public void testLineCommentWithString() throws IOException { 70 | 71 | final InputStream stream = this.getStream(R.raw.line_comment_with_string); 72 | List commands = SqlParser.parse(stream); 73 | 74 | assertEquals(1, commands.size()); 75 | assertEquals(sql1, commands.get(0)); 76 | } 77 | 78 | /** 79 | * Should be able to handle a line comment that contains a semicolon. 80 | * @throws IOException 81 | */ 82 | public void testLineCommentWithSemicolon() throws IOException { 83 | 84 | final InputStream stream = this.getStream(R.raw.line_comment_with_semicolon); 85 | List commands = SqlParser.parse(stream); 86 | 87 | assertEquals(1, commands.size()); 88 | assertEquals(sql1, commands.get(0)); 89 | } 90 | 91 | /** 92 | * Should ignore a block comment end token inside a line comment. 93 | * @throws IOException 94 | */ 95 | public void testLineAndBlockEndComment() throws IOException { 96 | 97 | final InputStream stream = this.getStream(R.raw.line_comment_and_block_end); 98 | List commands = SqlParser.parse(stream); 99 | 100 | assertEquals(1, commands.size()); 101 | assertEquals(sql1, commands.get(0)); 102 | } 103 | 104 | /** 105 | * Should be able to handle a block comment. 106 | * @throws IOException 107 | */ 108 | public void testBlockComment() throws IOException { 109 | 110 | final InputStream stream = this.getStream(R.raw.block_comment); 111 | List commands = SqlParser.parse(stream); 112 | 113 | assertEquals(1, commands.size()); 114 | assertEquals(sql1, commands.get(0)); 115 | } 116 | 117 | /** 118 | * Should be able to handle a block comment that contains string tokens. 119 | * @throws IOException 120 | */ 121 | public void testBlockCommentWithString() throws IOException { 122 | 123 | final InputStream stream = this.getStream(R.raw.block_comment_with_string); 124 | List commands = SqlParser.parse(stream); 125 | 126 | assertEquals(1, commands.size()); 127 | assertEquals(sql1, commands.get(0)); 128 | } 129 | 130 | /** 131 | * Should be able to handle a block comment that contains a semicolon. 132 | * @throws IOException 133 | */ 134 | public void testBlockCommentWithSemicolon() throws IOException { 135 | 136 | final InputStream stream = this.getStream(R.raw.block_comment_with_semicolon); 137 | List commands = SqlParser.parse(stream); 138 | 139 | assertEquals(1, commands.size()); 140 | assertEquals(sql1, commands.get(0)); 141 | } 142 | 143 | /** 144 | * Should ignore a line comment token inside a block comment. 145 | * @throws IOException 146 | */ 147 | public void testBlockAndLineComment() throws IOException { 148 | 149 | final InputStream stream = this.getStream(R.raw.block_with_line_comment); 150 | List commands = SqlParser.parse(stream); 151 | 152 | assertEquals(1, commands.size()); 153 | assertEquals(sql1, commands.get(0)); 154 | } 155 | 156 | /** 157 | * Should be able to parse a script that incorrectly closes a block comment twice. The 158 | * resulting script is not expected to run, but the parser shouldn't choke on it. 159 | * @throws IOException 160 | */ 161 | public void testInvalidBlockComment() throws IOException { 162 | 163 | final InputStream stream = this.getStream(R.raw.invalid_block_comment); 164 | List commands = SqlParser.parse(stream); 165 | 166 | assertEquals(1, commands.size()); 167 | assertEquals(invalid, commands.get(0)); 168 | } 169 | 170 | /** 171 | * Should ignore a line comment token inside a string. 172 | * @throws IOException 173 | */ 174 | public void testStringWithLineComment() throws IOException { 175 | final String sql = "INSERT INTO Entity ( Id, Column1, Column2 ) VALUES ( 1, '-- some text', 'some text' )"; 176 | 177 | final InputStream stream = this.getStream(R.raw.string_with_line_comment); 178 | List commands = SqlParser.parse(stream); 179 | 180 | assertEquals(1, commands.size()); 181 | assertEquals(sql, commands.get(0)); 182 | } 183 | 184 | /** 185 | * Should ignore block comment tokens inside strings. 186 | * @throws IOException 187 | */ 188 | public void testStringWithBlockComment() throws IOException { 189 | final String sql = "INSERT INTO Entity ( Id, Column1, Column2 ) VALUES ( 1, '/* some text', 'some text */' )"; 190 | 191 | final InputStream stream = this.getStream(R.raw.string_with_block_comment); 192 | List commands = SqlParser.parse(stream); 193 | 194 | assertEquals(1, commands.size()); 195 | assertEquals(sql, commands.get(0)); 196 | } 197 | 198 | /** 199 | * Should ignore semicolons inside strings. 200 | * @throws IOException 201 | */ 202 | public void testStringWithSemicolon() throws IOException { 203 | final String sql = "INSERT INTO Entity ( Id, Column1, Column2 ) VALUES ( 1, 'some ; text', 'some ; text' )"; 204 | 205 | final InputStream stream = this.getStream(R.raw.string_with_semicolon); 206 | List commands = SqlParser.parse(stream); 207 | 208 | assertEquals(1, commands.size()); 209 | assertEquals(sql, commands.get(0)); 210 | } 211 | 212 | /** 213 | * Should not clobber whitespace in strings. 214 | * @throws IOException 215 | */ 216 | public void testStringWithWhitespace() throws IOException { 217 | final String sql = "INSERT INTO Entity ( Id, Column1, Column2 ) VALUES ( 1, 'some\t\t\ttext', 'some text' )"; 218 | 219 | final InputStream stream = this.getStream(R.raw.string_with_whitespace); 220 | List commands = SqlParser.parse(stream); 221 | 222 | assertEquals(1, commands.size()); 223 | assertEquals(sql, commands.get(0)); 224 | } 225 | 226 | /** 227 | * Should be able to handle a script that contains anything nasty I can thing of right now. 228 | * @throws IOException 229 | */ 230 | public void testComplex() throws IOException { 231 | final String sql1 = "CREATE TABLE Entity2 ( Id INTEGER AUTO_INCREMENT PRIMARY KEY, Column TEXT NOT NULL, Column2 INTEGER NULL )"; 232 | final String sql2 = "INSERT INTO Entity2 ( Id, Column, Column2 ) SELECT Id, Column, 0 FROM Entity"; 233 | final String sql3 = "DROP TABLE Entity"; 234 | final String sql4 = "ALTER TABLE Entity2 RENAME TO Entity"; 235 | final String sql5 = "INSERT INTO Entity2 ( Id, Column, Column2) VALUES ( 9001 , 42, 'string /* string */ -- string' )"; 236 | 237 | final InputStream stream = this.getStream(R.raw.complex); 238 | List commands = SqlParser.parse(stream); 239 | 240 | assertEquals(5, commands.size()); 241 | assertEquals(sql1, commands.get(0)); 242 | assertEquals(sql2, commands.get(1)); 243 | assertEquals(sql3, commands.get(2)); 244 | assertEquals(sql4, commands.get(3)); 245 | assertEquals(sql5, commands.get(4)); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/CountTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.test.query; 3 | 4 | import com.activeandroid.query.Delete; 5 | import com.activeandroid.query.From; 6 | import com.activeandroid.query.Select; 7 | import com.activeandroid.test.MockModel; 8 | 9 | import java.util.List; 10 | 11 | 12 | public class CountTest extends SqlableTestCase { 13 | 14 | private void cleanTable() { 15 | new Delete().from(MockModel.class).execute(); 16 | } 17 | 18 | private void populateTable() { 19 | MockModel m1 = new MockModel(); 20 | MockModel m2 = new MockModel(); 21 | MockModel m3 = new MockModel(); 22 | 23 | m1.intField = 1; 24 | m2.intField = 1; 25 | m3.intField = 2; 26 | 27 | m1.save(); 28 | m2.save(); 29 | m3.save(); 30 | } 31 | 32 | /** 33 | * Should be a simple count for the entire table. 34 | */ 35 | public void testCountTableSql() { 36 | final String expected = "SELECT COUNT(*) FROM MockModel"; 37 | 38 | String actual = new Select() 39 | .from(MockModel.class) 40 | .toCountSql(); 41 | 42 | assertEquals(expected, actual); 43 | } 44 | 45 | /** 46 | * Should be a count with the specified where-clause. 47 | */ 48 | public void testCountWhereClauseSql() { 49 | final String expected = "SELECT COUNT(*) FROM MockModel WHERE intField = ?"; 50 | 51 | String actual = new Select() 52 | .from(MockModel.class) 53 | .where("intField = ?", 1) 54 | .toCountSql(); 55 | 56 | assertEquals(expected, actual); 57 | } 58 | 59 | /** 60 | * Shouldn't include order by as it has no influence on the result of count and 61 | * should improve performance. 62 | */ 63 | public void testCountOrderBySql() { 64 | final String expected = "SELECT COUNT(*) FROM MockModel WHERE intField <> ? GROUP BY intField"; 65 | 66 | String actual = new Select() 67 | .from(MockModel.class) 68 | .where("intField <> ?", 0) 69 | .orderBy("intField") 70 | .groupBy("intField") 71 | .toCountSql(); 72 | 73 | assertEquals(expected, actual); 74 | } 75 | 76 | /** 77 | * Should return the same count as there are entries in the result set/table. 78 | */ 79 | public void testCountTable() { 80 | cleanTable(); 81 | populateTable(); 82 | 83 | From from = new Select() 84 | .from(MockModel.class); 85 | 86 | final List list = from.execute(); 87 | final int count = from.count(); 88 | 89 | assertEquals(3, count); 90 | assertEquals(list.size(), count); 91 | } 92 | 93 | /** 94 | * Should return the same count as there are entries in the result set if the where-clause 95 | * matches several entries. 96 | */ 97 | public void testCountWhereClause() { 98 | cleanTable(); 99 | populateTable(); 100 | 101 | From from = new Select() 102 | .from(MockModel.class) 103 | .where("intField = ?", 1); 104 | 105 | final List list = from.execute(); 106 | final int count = from.count(); 107 | 108 | assertEquals(2, count); 109 | assertEquals(list.size(), count); 110 | } 111 | 112 | /** 113 | * Should return the same count as there are entries in the result set if the where-clause 114 | * matches zero entries. 115 | */ 116 | public void testCountEmptyResult() { 117 | cleanTable(); 118 | populateTable(); 119 | 120 | From from = new Select() 121 | .from(MockModel.class) 122 | .where("intField = ?", 3); 123 | 124 | final List list = from.execute(); 125 | final int count = from.count(); 126 | 127 | assertEquals(0, count); 128 | assertEquals(list.size(), count); 129 | } 130 | 131 | /** 132 | * Should not change the result if order by is used. 133 | */ 134 | public void testCountOrderBy() { 135 | cleanTable(); 136 | populateTable(); 137 | 138 | From from = new Select() 139 | .from(MockModel.class) 140 | .where("intField = ?", 1) 141 | .orderBy("intField ASC"); 142 | 143 | final List list = from.execute(); 144 | final int count = from.count(); 145 | 146 | assertEquals(2, count); 147 | assertEquals(list.size(), count); 148 | } 149 | 150 | /** 151 | * Should return the total number of rows, even if the rows are grouped. May seem weird, just 152 | * test it in an SQL explorer. 153 | */ 154 | public void testCountGroupBy() { 155 | cleanTable(); 156 | populateTable(); 157 | 158 | From from = new Select() 159 | .from(MockModel.class) 160 | .groupBy("intField") 161 | .having("intField = 1"); 162 | 163 | final List list = from.execute(); 164 | final int count = from.count(); 165 | 166 | assertEquals(2, count); 167 | assertEquals(1, list.size()); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/DeleteTest.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.query.Delete; 20 | import com.activeandroid.test.MockModel; 21 | 22 | public class DeleteTest extends SqlableTestCase { 23 | public void testDelete() { 24 | assertSqlEquals("DELETE ", new Delete()); 25 | } 26 | 27 | public void testFrom() { 28 | assertSqlEquals("DELETE FROM MockModel", 29 | new Delete().from(MockModel.class)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/ExistsTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.activeandroid.test.query; 3 | 4 | import com.activeandroid.query.Delete; 5 | import com.activeandroid.query.From; 6 | import com.activeandroid.query.Select; 7 | import com.activeandroid.test.MockModel; 8 | 9 | import java.util.List; 10 | 11 | 12 | public class ExistsTest extends SqlableTestCase { 13 | 14 | private void cleanTable() { 15 | new Delete().from(MockModel.class).execute(); 16 | } 17 | 18 | private void populateTable() { 19 | MockModel m1 = new MockModel(); 20 | MockModel m2 = new MockModel(); 21 | MockModel m3 = new MockModel(); 22 | 23 | m1.intField = 1; 24 | m2.intField = 1; 25 | m3.intField = 2; 26 | 27 | m1.save(); 28 | m2.save(); 29 | m3.save(); 30 | } 31 | 32 | /** 33 | * Should return {@code true} since the result set/table isn't empty. 34 | */ 35 | public void testExistsTable() { 36 | cleanTable(); 37 | populateTable(); 38 | 39 | From from = new Select() 40 | .from(MockModel.class); 41 | 42 | final List list = from.execute(); 43 | final boolean exists = from.exists(); 44 | 45 | assertTrue(exists); 46 | assertTrue(list.size() > 0); 47 | } 48 | 49 | /** 50 | * Should be a simple exists for the entire table. 51 | */ 52 | public void testCountTableSql() { 53 | final String expected = "SELECT EXISTS(SELECT 1 FROM MockModel )"; 54 | 55 | String actual = new Select() 56 | .from(MockModel.class) 57 | .toExistsSql(); 58 | 59 | assertEquals(expected, actual); 60 | } 61 | 62 | /** 63 | * Should be an exists with the specified where-clause. 64 | */ 65 | public void testCountWhereClauseSql() { 66 | final String expected = "SELECT EXISTS(SELECT 1 FROM MockModel WHERE intField = ? )"; 67 | 68 | String actual = new Select() 69 | .from(MockModel.class) 70 | .where("intField = ?", 1) 71 | .toExistsSql(); 72 | 73 | assertEquals(expected, actual); 74 | } 75 | 76 | /** 77 | * Shouldn't include order by as it has no influence on the result of exists and 78 | * should improve performance. 79 | */ 80 | public void testCountOrderBySql() { 81 | final String expected = "SELECT EXISTS(SELECT 1 FROM MockModel WHERE intField <> ? GROUP BY intField )"; 82 | 83 | String actual = new Select() 84 | .from(MockModel.class) 85 | .groupBy("intField") 86 | .orderBy("intField") 87 | .where("intField <> ?", 0) 88 | .toExistsSql(); 89 | 90 | assertEquals(expected, actual); 91 | } 92 | 93 | /** 94 | * Should return {@code true} since the where-clause matches rows and thus the result set isn't 95 | * empty. 96 | */ 97 | public void testExistsWhereClause() { 98 | cleanTable(); 99 | populateTable(); 100 | 101 | From from = new Select() 102 | .from(MockModel.class) 103 | .where("intField = ?", 1); 104 | 105 | final List list = from.execute(); 106 | final boolean exists = from.exists(); 107 | 108 | assertTrue(exists); 109 | assertTrue(list.size() > 0); 110 | } 111 | 112 | /** 113 | * Should return {@code false} since the where-clause matches zero rows and thus the result set 114 | * is empty. 115 | */ 116 | public void testExistsEmptyResult() { 117 | cleanTable(); 118 | populateTable(); 119 | 120 | From from = new Select() 121 | .from(MockModel.class) 122 | .where("intField = ?", 3); 123 | 124 | final List list = from.execute(); 125 | final boolean exists = from.exists(); 126 | 127 | assertFalse(exists); 128 | assertFalse(list.size() > 0); 129 | } 130 | 131 | /** 132 | * Should not change the result if order by is used. 133 | */ 134 | public void testCountOrderBy() { 135 | cleanTable(); 136 | populateTable(); 137 | 138 | From from = new Select() 139 | .from(MockModel.class) 140 | .where("intField = ?", 1) 141 | .orderBy("intField ASC"); 142 | 143 | final List list = from.execute(); 144 | final boolean exists = from.exists(); 145 | 146 | assertTrue(exists); 147 | assertTrue(list.size() > 0); 148 | } 149 | 150 | /** 151 | * Should not change the result if group by is used. 152 | */ 153 | public void testCountGroupBy() { 154 | cleanTable(); 155 | populateTable(); 156 | 157 | From from = new Select() 158 | .from(MockModel.class) 159 | .groupBy("intField") 160 | .having("intField = 1"); 161 | 162 | final List list = from.execute(); 163 | final boolean exists = from.exists(); 164 | 165 | assertTrue(exists); 166 | assertTrue(list.size() > 0); 167 | } 168 | 169 | /** 170 | * Should not exist if group by eliminates all rows. 171 | */ 172 | public void testCountGroupByEmpty() { 173 | cleanTable(); 174 | populateTable(); 175 | 176 | From from = new Select() 177 | .from(MockModel.class) 178 | .groupBy("intField") 179 | .having("intField = 3"); 180 | 181 | final List list = from.execute(); 182 | final boolean exists = from.exists(); 183 | 184 | assertFalse(exists); 185 | assertFalse(list.size() > 0); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/FromTest.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.Model; 20 | import com.activeandroid.annotation.Table; 21 | import com.activeandroid.query.From; 22 | import com.activeandroid.query.Select; 23 | import com.activeandroid.test.MockModel; 24 | 25 | public class FromTest extends SqlableTestCase { 26 | private static final String SELECT_PREFIX = "SELECT ALL * FROM MockModel "; 27 | 28 | public void testLimit() { 29 | assertSqlEquals(SELECT_PREFIX + "LIMIT 10", 30 | from().limit(10)); 31 | assertSqlEquals(SELECT_PREFIX + "LIMIT 10", 32 | from().limit("10")); 33 | } 34 | 35 | public void testOffset() { 36 | assertSqlEquals(SELECT_PREFIX + "OFFSET 10", 37 | from().offset(10)); 38 | assertSqlEquals(SELECT_PREFIX + "OFFSET 10", 39 | from().offset("10")); 40 | } 41 | 42 | public void testLimitOffset() { 43 | assertSqlEquals(SELECT_PREFIX + "LIMIT 10 OFFSET 20", 44 | from().offset(20).limit(10)); 45 | assertSqlEquals(SELECT_PREFIX + "LIMIT 10 OFFSET 20", 46 | from().limit(10).offset(20)); 47 | } 48 | 49 | public void testAs() { 50 | assertSqlEquals(SELECT_PREFIX + "AS a", 51 | from().as("a")); 52 | } 53 | 54 | public void testOrderBy() { 55 | assertSqlEquals(SELECT_PREFIX + "ORDER BY Id DESC", 56 | from().orderBy("Id DESC")); 57 | } 58 | 59 | public void testWhereNoArguments() { 60 | assertSqlEquals(SELECT_PREFIX + "WHERE Id = 5", 61 | from().where("Id = 5")); 62 | 63 | assertSqlEquals(SELECT_PREFIX + "WHERE Id = 1 AND Id = 2 AND Id = 5", 64 | from().where("Id = 1").where("Id = 2").where("Id = 5")); 65 | } 66 | 67 | public void testWhereWithArguments() { 68 | From query = from().where("Id = ?", 5); 69 | assertArrayEquals(query.getArguments(), "5"); 70 | assertSqlEquals(SELECT_PREFIX + "WHERE Id = ?", 71 | query); 72 | 73 | query = from().where("Id > ? AND Id < ?", 5, 10); 74 | assertArrayEquals(query.getArguments(), "5", "10"); 75 | assertSqlEquals(SELECT_PREFIX + "WHERE Id > ? AND Id < ?", 76 | query); 77 | 78 | // Chained 79 | query = from() 80 | .where("Id != ?", 10) 81 | .where("Id IN (?, ?, ?)", 5, 10, 15) 82 | .where("Id > ? AND Id < ?", 5, 10); 83 | assertArrayEquals(query.getArguments(), "10", "5", "10", "15", "5", "10"); 84 | assertSqlEquals(SELECT_PREFIX + "WHERE Id != ? AND Id IN (?, ?, ?) AND Id > ? AND Id < ?", 85 | query); 86 | } 87 | 88 | public void testWhereChaining() { 89 | 90 | From expected = from() 91 | .where("a = ? AND b = ?", 1, 2); 92 | 93 | From actual = from() 94 | .where("a = ?", 1, 2) 95 | .where("b = ?", 1, 2); 96 | 97 | assertSqlEquals(expected, actual); 98 | } 99 | 100 | public void testWhereAndChaining() { 101 | 102 | From expected = from() 103 | .where("a = ? AND b = ?", 1, 2); 104 | 105 | From actual = from() 106 | .where("a = ?", 1) 107 | .and("b = ?", 2); 108 | 109 | assertSqlEquals(expected, actual); 110 | } 111 | 112 | public void testWhereOrChaining() { 113 | 114 | From expected = from() 115 | .where("a = ? OR b = ?", 1, 2); 116 | 117 | From actual = from() 118 | .where("a = ?", 1) 119 | .or("b = ?", 2); 120 | 121 | assertSqlEquals(expected, actual); 122 | } 123 | 124 | public void testWhereAndOrChaining() { 125 | 126 | From expected = from() 127 | .where("a = ? OR (b = ? AND c = ?)", 1, 2, 3); 128 | 129 | From actual = from() 130 | .where("a = ?", 1) 131 | .or("(b = ? AND c = ?)", 2, 3); 132 | 133 | assertSqlEquals(expected, actual); 134 | } 135 | 136 | public void testWhereAlternateAndOrChaining() { 137 | 138 | From expected = from() 139 | .where("a = ? OR (b = ? AND c = ?)", 1, 2, 3); 140 | 141 | From actual = from() 142 | .where("a = ?", 1) 143 | .or("(b = ?", 2) 144 | .and("c = ?)", 3); 145 | 146 | assertSqlEquals(expected, actual); 147 | } 148 | 149 | // Test with 'no arguments' and 'with arguments' chained together. 150 | public void testWhereWithNoArgumentsAndWithArguments() { 151 | From query = from().where("Id = 5"); 152 | query.where("Id > ?", 4); 153 | assertArrayEquals(query.getArguments(), "4"); 154 | assertSqlEquals(SELECT_PREFIX + "WHERE Id = 5 AND Id > ?", 155 | query); 156 | } 157 | 158 | public void testSingleJoin() { 159 | assertSqlEquals(SELECT_PREFIX + "JOIN JoinModel ON MockModel.Id = JoinModel.Id", 160 | from().join(JoinModel.class).on("MockModel.Id = JoinModel.Id")); 161 | 162 | assertSqlEquals(SELECT_PREFIX + "AS a JOIN JoinModel AS b ON a.Id = b.Id", 163 | from().as("a").join(JoinModel.class).as("b").on("a.Id = b.Id")); 164 | 165 | assertSqlEquals(SELECT_PREFIX + "JOIN JoinModel USING (Id, other)", 166 | from().join(JoinModel.class).using("Id", "other")); 167 | } 168 | 169 | public void testJoins() { 170 | assertSqlEquals(SELECT_PREFIX + "JOIN JoinModel ON Id JOIN JoinModel2 ON Id", 171 | from().join(JoinModel.class).on("Id") 172 | .join(JoinModel2.class).on("Id")); 173 | } 174 | 175 | public void testJoinTypes() { 176 | assertSqlEquals(SELECT_PREFIX + "INNER JOIN JoinModel ON", 177 | from().innerJoin(JoinModel.class).on("")); 178 | assertSqlEquals(SELECT_PREFIX + "OUTER JOIN JoinModel ON", 179 | from().outerJoin(JoinModel.class).on("")); 180 | assertSqlEquals(SELECT_PREFIX + "CROSS JOIN JoinModel ON", 181 | from().crossJoin(JoinModel.class).on("")); 182 | } 183 | 184 | public void testGroupByHaving() { 185 | assertSqlEquals(SELECT_PREFIX + "GROUP BY Id", 186 | from().groupBy("Id")); 187 | assertSqlEquals(SELECT_PREFIX + "GROUP BY Id HAVING Id = 1", 188 | from().groupBy("Id").having("Id = 1")); 189 | assertSqlEquals(SELECT_PREFIX + "GROUP BY Id HAVING Id = 1", 190 | from().having("Id = 1").groupBy("Id")); 191 | } 192 | 193 | public void testAll() { 194 | final String expectedSql = SELECT_PREFIX + "AS a JOIN JoinModel USING (Id) WHERE Id > 5 GROUP BY Id HAVING Id < 10 LIMIT 5 OFFSET 10"; 195 | 196 | // Try a few different orderings, shouldn't change the output 197 | assertSqlEquals(expectedSql, 198 | from() 199 | .as("a") 200 | .where("Id > 5") 201 | .join(JoinModel.class).using("Id") 202 | .groupBy("Id") 203 | .having("Id < 10") 204 | .limit(5) 205 | .offset(10)); 206 | assertSqlEquals(expectedSql, 207 | from() 208 | .offset(10) 209 | .having("Id < 10") 210 | .join(JoinModel.class).using("Id") 211 | .limit(5) 212 | .as("a") 213 | .where("Id > 5") 214 | .groupBy("Id")); 215 | assertSqlEquals(expectedSql, 216 | from() 217 | .join(JoinModel.class).using("Id") 218 | .offset(10) 219 | .having("Id < 10") 220 | .where("Id > 5") 221 | .groupBy("Id") 222 | .limit(5) 223 | .as("a")); 224 | } 225 | 226 | private From from() { 227 | return new Select().all().from(MockModel.class); 228 | } 229 | 230 | @Table(name = "JoinModel") 231 | private static class JoinModel extends Model { 232 | } 233 | 234 | @Table(name = "JoinModel2") 235 | private static class JoinModel2 extends Model { 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/SelectTest.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.query.Select; 20 | import com.activeandroid.test.MockModel; 21 | 22 | public class SelectTest extends SqlableTestCase { 23 | public void testSelectEmpty() { 24 | assertSqlEquals("SELECT * ", new Select()); 25 | } 26 | 27 | public void testSelectAll() { 28 | assertSqlEquals("SELECT ALL * ", new Select().all()); 29 | assertSqlEquals("SELECT ALL * ", new Select().distinct().all()); 30 | } 31 | 32 | public void testSelectDistinct() { 33 | assertSqlEquals("SELECT DISTINCT * ", new Select().distinct()); 34 | assertSqlEquals("SELECT DISTINCT * ", new Select().all().distinct()); 35 | } 36 | 37 | public void testSelectStringColumns() { 38 | assertSqlEquals("SELECT a, b, c ", new Select("a", "b", "c")); 39 | } 40 | 41 | public void testSelectDistinctColumns() { 42 | assertSqlEquals("SELECT DISTINCT a, b, c ", 43 | new Select("a", "b", "c").distinct()); 44 | } 45 | 46 | public void testFrom() { 47 | assertSqlEquals("SELECT ALL * FROM MockModel", 48 | new Select().all().from(MockModel.class)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/SqlableTestCase.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.query.Sqlable; 20 | import com.activeandroid.test.ActiveAndroidTestCase; 21 | 22 | public abstract class SqlableTestCase extends ActiveAndroidTestCase { 23 | public static void assertSqlEquals(String expected, Sqlable actual) { 24 | assertEquals(expected, actual.toSql()); 25 | } 26 | 27 | public static void assertSqlEquals(Sqlable expected, Sqlable actual) { 28 | assertEquals(expected.toSql(), actual.toSql()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/src/com/activeandroid/test/query/UpdateTest.java: -------------------------------------------------------------------------------- 1 | package com.activeandroid.test.query; 2 | 3 | /* 4 | * Copyright (C) 2010 Michael Pardo 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import com.activeandroid.query.Set; 20 | import com.activeandroid.query.Update; 21 | import com.activeandroid.test.MockModel; 22 | 23 | public class UpdateTest extends SqlableTestCase { 24 | private static final String UPDATE_PREFIX = "UPDATE MockModel "; 25 | 26 | public void testUpdate() { 27 | assertSqlEquals(UPDATE_PREFIX, update()); 28 | } 29 | 30 | public void testUpdateSet() { 31 | assertSqlEquals(UPDATE_PREFIX + "SET Id = 5 ", 32 | update().set("Id = 5")); 33 | } 34 | 35 | public void testUpdateWhereNoArguments() { 36 | assertSqlEquals(UPDATE_PREFIX + "SET Id = 5 WHERE Id = 1 ", 37 | update() 38 | .set("Id = 5") 39 | .where("Id = 1")); 40 | } 41 | 42 | public void testUpdateWhereWithArguments() { 43 | Set set = update() 44 | .set("Id = 5") 45 | .where("Id = ?", 1); 46 | assertArrayEquals(set.getArguments(), "1"); 47 | assertSqlEquals(UPDATE_PREFIX + "SET Id = 5 WHERE Id = ? ", 48 | set); 49 | 50 | set = update() 51 | .set("Id = 5") 52 | .where("Id = ?", 1) 53 | .where("Id IN (?, ?, ?)", 5, 4, 3); 54 | assertArrayEquals(set.getArguments(), "5", "4", "3"); 55 | assertSqlEquals(UPDATE_PREFIX + "SET Id = 5 WHERE Id IN (?, ?, ?) ", 56 | set); 57 | } 58 | 59 | private Update update() { 60 | return new Update(MockModel.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /wait_for_emulator: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bootanim="" 4 | failcounter=0 5 | until [[ "$bootanim" =~ "stopped" ]]; do 6 | bootanim=`adb -e shell getprop init.svc.bootanim 2>&1` 7 | echo "$bootanim" 8 | if [[ "$bootanim" =~ "not found" ]]; then 9 | let "failcounter += 1" 10 | if [[ $failcounter -gt 3 ]]; then 11 | echo "Failed to start emulator" 12 | exit 1 13 | fi 14 | fi 15 | sleep 1 16 | done 17 | 18 | echo "Done" 19 | --------------------------------------------------------------------------------