├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── bintray.gradle ├── build.gradle ├── gradle.properties ├── jacoco.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ ├── AndroidManifest.xml │ └── java │ │ └── ru │ │ └── arturvasilov │ │ └── sqlite │ │ ├── core │ │ ├── ContentProviderErrorsTest.java │ │ └── SQLiteTest.java │ │ ├── rx │ │ └── RxSQLiteTest.java │ │ ├── testutils │ │ ├── RxUtils.java │ │ ├── SQLiteProvider.java │ │ ├── TestObject.java │ │ └── TestTable.java │ │ └── utils │ │ └── EmptyTableTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── ru │ │ └── arturvasilov │ │ └── sqlite │ │ ├── core │ │ ├── BaseTable.java │ │ ├── BasicTableObserver.java │ │ ├── ContentTableObserver.java │ │ ├── DatabaseObserver.java │ │ ├── MainHandler.java │ │ ├── Observers.java │ │ ├── SQLite.java │ │ ├── SQLiteConfig.java │ │ ├── SQLiteContentProvider.java │ │ ├── SQLiteHelper.java │ │ ├── SQLiteSchema.java │ │ ├── Table.java │ │ └── Where.java │ │ ├── rx │ │ ├── RxSQLite.java │ │ └── TableObservable.java │ │ └── utils │ │ ├── SQLiteUtils.java │ │ └── TableBuilder.java │ └── test │ └── java │ └── ru │ └── arturvasilov │ └── sqlite │ ├── core │ └── WhereTest.java │ ├── testutils │ ├── JUnitTestObject.java │ └── JUnitTestTable.java │ └── utils │ ├── SQLiteUtilsTest.java │ └── TableBuilderTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/** 13 | gen/** 14 | out/** 15 | gen-external-apklibs/** 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # IntellijJ IDEA 21 | *.iml 22 | *.ipr 23 | *.iws 24 | .idea/** 25 | classes/** 26 | target/** 27 | 28 | # Mac OS 29 | .DS_Store 30 | 31 | # Gradle 32 | gradle-app.setting 33 | .gradletasknamecache 34 | .gradle/** 35 | build/** 36 | 37 | # Proguard 38 | proguard/** 39 | 40 | # Trash 41 | trash/** 42 | 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | sudo: false 7 | cache: 8 | directories: 9 | - $HOME/.gradle/caches/3.3 10 | - $HOME/.gradle/caches/jars-1 11 | - $HOME/.gradle/daemon 12 | - $HOME/.gradle/native 13 | - $HOME/.gradle/wrapper 14 | 15 | android: 16 | components: 17 | - platform-tools 18 | - tools 19 | - build-tools-25.0.2 20 | - android-25 21 | - extra-google-google_play_services 22 | - extra-android-support 23 | - extra-android-m2repository 24 | - sys-img-armeabi-v7a-android-21 25 | 26 | # Emulator Management: Create, Start and Wait 27 | before_script: 28 | - echo no | android create avd --force -n test -t android-21 --abi armeabi-v7a 29 | - emulator -avd test -no-skin -no-audio -no-window & 30 | - android-wait-for-emulator 31 | - adb shell input keyevent 82 & 32 | 33 | script: 34 | - ./gradlew clean :app:testDebugUnitTest :app:createDebugCoverageReport :app:jacocoTestReport :app:coveralls 35 | 36 | notifications: 37 | email: false 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLite [![Apache License](https://img.shields.io/badge/license-Apache%20v2-blue.svg)](https://github.com/ArturVasilov/SQLite/blob/master/LICENSE) [![Build Status](https://travis-ci.org/ArturVasilov/SQLite.png?branch=master)](https://github.com/ArturVasilov/SQLite) [![Coverage Status](https://coveralls.io/repos/github/ArturVasilov/SQLite/badge.svg#3)](https://coveralls.io/github/ArturVasilov/SQLite) 2 | 3 | #### Yet another Android library for database 4 | 5 | Database library for Android based on SQLite and ContentProvider, which provides simple way for all operations with data. 6 | 7 | ### Advantages: 8 | 9 | * 0 reflection 10 | * Full customization (since library is a simple wrapper on ContentProvider you can always have direct access to it) 11 | * Flexible interface for manipulating data 12 | * Data migration 13 | * RxJava support 14 | 15 | ### Gradle 16 | 17 | ```groovy 18 | compile 'ru.arturvasilov:sqlite:0.2.0' 19 | ``` 20 | 21 | ### Tables: 22 | 23 | Instead of generating code and using your model classes for database directly, this library uses tables classes for each table in database (or in fact for each class you want to store). It's routine to write these classes but it also give you more control, which is useful for features like data migration. 24 | 25 | So for each table in database you have to create a class which extends ```Table``` interface or ```BaseTable``` class like this: 26 | 27 | ```java 28 | public class PersonTable extends BaseTable { 29 | 30 | public static final Table TABLE = new PersonTable(); 31 | 32 | public static final String ID = "id"; 33 | public static final String NAME = "name"; 34 | public static final String AGE = "age"; 35 | 36 | @Override 37 | public void onCreate(@NonNull SQLiteDatabase database) { 38 | TableBuilder.create(this) 39 | .intColumn(ID) 40 | .textColumn(NAME) 41 | .intColumn(AGE) 42 | .primaryKey(ID) 43 | .execute(database); 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public ContentValues toValues(@NonNull Person person) { 49 | ContentValues values = new ContentValues(); 50 | values.put(ID, person.getId()); 51 | values.put(NAME, person.getName()); 52 | values.put(AGE, person.getAge()); 53 | return values; 54 | } 55 | 56 | @NonNull 57 | @Override 58 | public Person fromCursor(@NonNull Cursor cursor) { 59 | int id = cursor.getInt(cursor.getColumnIndex(ID)); 60 | String name = cursor.getString(cursor.getColumnIndex(NAME)); 61 | int age = cursor.getInt(cursor.getColumnIndex(AGE)); 62 | return new Person(id, name, age); 63 | } 64 | } 65 | ``` 66 | 67 | ### Setting Up 68 | 69 | After creating tables your need to specify ContentProvider, where you add these tables: 70 | ```java 71 | public class SQLiteProvider extends SQLiteContentProvider { 72 | 73 | private static final String DATABASE_NAME = "mydatabase.db"; 74 | private static final String CONTENT_AUTHORITY = "com.myapp"; 75 | 76 | @Override 77 | protected void prepareConfig(@NonNull SQLiteConfig config) { 78 | config.setDatabaseName(DATABASE_NAME); 79 | config.setAuthority(CONTENT_AUTHORITY); 80 | } 81 | 82 | @Override 83 | protected void prepareSchema(@NonNull Schema schema) { 84 | schema.register(PersonTable.TABLE); 85 | } 86 | } 87 | ``` 88 | 89 | And register it in the AndroidManifest.xml: 90 | ```xml 91 | 95 | ``` 96 | 97 | And initialize SQLite: 98 | ```java 99 | public class MyApplication extends Application { 100 | 101 | @Override 102 | public void onCreate() { 103 | super.onCreate(); 104 | SQLite.initialize(this); 105 | } 106 | } 107 | ``` 108 | 109 | ### Supported data types 110 | 111 | 1. _int_, _short_ and _long_ are supported with intColumn method 112 | 2. _boolean_ is also supported with ```intColumn``` method, but you still have to manually convert it in ```Table#toValues``` and ```Table#fromCursor``` methods 113 | 3. _double_ and _float_ are supported with ```realColumn```. 114 | 4. String, enums and custom objects should be saved as TEXT with ```textColumn``` method. For custom objects you may consider json serialization (relations is not in the nearest plan). 115 | 116 | ### Operations 117 | 118 | All operations should go through the SQLite or RxSQLite classes. You can access to ContentProvider directly, but note that you can loose features like observing changes in tables. 119 | 120 | Every operation (query, insert, update, delete) exist both in direct and rx ways. 121 | 122 | You can query for data like so: 123 | ```java 124 | Person person = SQLite.get().queryObject(PersonTable.TABLE); 125 | // or for list 126 | List persons = SQLite.get().query(PersonTable.TABLE); 127 | // or with where 128 | List adults = SQLite.get().query(PersonTable.TABLE, Where.create().greaterThanOrEqualTo(PersonTable.AGE, 18)); 129 | ``` 130 | 131 | Similar way for RxSQLite: 132 | 133 | ```java 134 | RxSQLite.get().query(PersonTable.TABLE) 135 | .subscribe(persons -> { 136 | //do something with persons 137 | }); 138 | ``` 139 | 140 | *Note*: RxSQLite doesn't take care about doing operations in background - it's up to your. 141 | 142 | And it's all the same for other operations. 143 | 144 | ### Observing changes 145 | 146 | Observing changes in database is a great way for communication between your UI classes and network layer. This library provides flexible implementation of this pattern. 147 | 148 | Note that starting from version 0.1.3 automatic notifications are disabled by default. 149 | You can either update call ```SQLite.get().notifyTableChanged(Table)``` manually or enable automatic notifications 150 | (notifications will be send for each single operation) with ```SQLite.get().enableAutomaticNotifications()```. 151 | 152 | Notifications about changes in table are called in the main thread. 153 | 154 | Get notified when table changed: 155 | ```java 156 | public class MainActivity extends AppCompatActivity implements BasicTableObserver { 157 | 158 | // ... 159 | 160 | @Override 161 | protected void onResume() { 162 | super.onResume(); 163 | SQLite.get().registerObserver(PersonTable.TABLE, this); 164 | } 165 | 166 | @Override 167 | protected void onPause() { 168 | super.onPause(); 169 | SQLite.get().unregisterObserver(this); 170 | } 171 | 172 | @Override 173 | public void onTableChanged() { 174 | // You now know that content in the table has changed 175 | } 176 | 177 | } 178 | ``` 179 | 180 | If you also want to get all the content from database (typical for the observing changes), you can change *BasicTableObserver* to *ContentTableObserver* and implement it's method: 181 | ```java 182 | @Override 183 | public void onTableChanged(@NonNull List persons) { 184 | // handle changed persons 185 | } 186 | ``` 187 | Everything else is the same! And more, you don't need to care about performance, for these changes library reads queries tables in the background already. *Note*: that's why you should be careful using this type of subscribption - frequent changes in table may affect your app. 188 | 189 | It's even more flexible with RxSQLite: 190 | ```java 191 | private Disposable mPersonsDisposable; 192 | 193 | //... 194 | 195 | @Override 196 | protected void onResume() { 197 | super.onResume(); 198 | mPersonsDisposable = RxSQLite.get().observeChanges(PersonTable.TABLE) 199 | .subscribe(value -> { 200 | // table changed 201 | }); 202 | } 203 | 204 | @Override 205 | protected void onPause() { 206 | super.onPause(); 207 | mPersonsDisposable.dispose(); 208 | } 209 | ``` 210 | 211 | You can also query all the table with one simple call: 212 | ```java 213 | mPersonsDisposable = RxSQLite.get().observeChanges(PersonTable.TABLE).withQuery().subscribe(persons -> {}); 214 | ``` 215 | 216 | *Note* you still have to manage subscription manually. 217 | 218 | ### Data migration 219 | 220 | Data migration is always is most painful part. Library provides you a way to update the table and decide how it should be updated. 221 | 222 | Each table has method ```getLastUpgradeVersion```, which by default returns 1. Current database version is the maximum of all tables versions. 223 | 224 | If you changed any table, simple update it's version. All others table won't be affected, if their version is less than maximum: 225 | ```java 226 | @Override 227 | public int getLastUpgradeVersion() { 228 | return 2; 229 | } 230 | ``` 231 | 232 | By default, *onUpdate* method simply recreates the table, but you can customize it by overriding this method: 233 | ```java 234 | @Override 235 | public void onUpgrade(@NonNull SQLiteDatabase database) { 236 | database.execSQL("DROP TABLE IF EXISTS " + getTableName()); 237 | onCreate(database); 238 | } 239 | ``` 240 | 241 | ### Samples: 242 | 243 | [StackDroid](https://github.com/ArturVasilov/StackDroid) application uses this library to work with database. 244 | 245 | ### Future plans 246 | 247 | 1. Support temporary entries in database 248 | 2. Generate most of boilerplate code 249 | 3. Support relations 250 | 4. Add triggers and functions 251 | 252 | ### Issues 253 | 254 | Feel free to create issues or even pull requests! -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/** 13 | gen/** 14 | out/** 15 | gen-external-apklibs/** 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # IntellijJ IDEA 21 | *.iml 22 | *.ipr 23 | *.iws 24 | .idea/** 25 | classes/** 26 | target/** 27 | 28 | # Mac OS 29 | .DS_Store 30 | 31 | # Gradle 32 | gradle-app.setting 33 | .gradletasknamecache 34 | .gradle/** 35 | build/** 36 | 37 | # Proguard 38 | proguard/** 39 | 40 | # Trash 41 | trash/** 42 | 43 | -------------------------------------------------------------------------------- /app/bintray.gradle: -------------------------------------------------------------------------------- 1 | // ./gradlew clean build generateRelease 2 | apply plugin: 'maven' 3 | 4 | def groupId = project.PUBLISH_GROUP_ID 5 | def artifactId = project.PUBLISH_ARTIFACT_ID 6 | def version = project.PUBLISH_VERSION 7 | 8 | def localReleaseDest = "${buildDir}/release/${version}" 9 | 10 | task androidJavadocs(type: Javadoc) { 11 | failOnError = false 12 | source = android.sourceSets.main.java.srcDirs 13 | ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" 14 | classpath += files(ext.androidJar) 15 | } 16 | 17 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 18 | classifier = 'javadoc' 19 | from androidJavadocs.destinationDir 20 | } 21 | 22 | task androidSourcesJar(type: Jar) { 23 | classifier = 'sources' 24 | from android.sourceSets.main.java.srcDirs 25 | } 26 | 27 | uploadArchives { 28 | repositories.mavenDeployer { 29 | pom.groupId = groupId 30 | pom.artifactId = artifactId 31 | pom.version = version 32 | // Add other pom properties here if you want (developer details / licenses) 33 | repository(url: "file://${localReleaseDest}") 34 | } 35 | } 36 | 37 | task zipRelease(type: Zip) { 38 | from localReleaseDest 39 | destinationDir buildDir 40 | archiveName "release-${version}.zip" 41 | } 42 | 43 | task generateRelease << { 44 | println "Release ${version} can be found at ${localReleaseDest}/" 45 | println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip" 46 | } 47 | 48 | generateRelease.dependsOn(uploadArchives) 49 | generateRelease.dependsOn(zipRelease) 50 | 51 | 52 | artifacts { 53 | archives androidSourcesJar 54 | archives androidJavadocsJar 55 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName project.PUBLISH_VERSION 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | debug { 21 | testCoverageEnabled true 22 | } 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_7 27 | targetCompatibility JavaVersion.VERSION_1_7 28 | } 29 | 30 | testOptions { 31 | unitTests { 32 | all { 33 | jvmArgs '-noverify' 34 | jacoco { 35 | includeNoLocationClasses = true 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | dependencies { 43 | compile 'ru.arturvasilov:sqlite-bindings:0.1.2' 44 | 45 | compile 'com.android.support:appcompat-v7:25.1.1' 46 | compile 'io.reactivex.rxjava2:rxjava:2.0.5' 47 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 48 | 49 | testCompile 'junit:junit:4.12' 50 | testCompile 'org.mockito:mockito-core:1.10.19' 51 | 52 | androidTestCompile 'com.android.support:support-annotations:25.1.1' 53 | androidTestCompile 'com.android.support.test:runner:0.5' 54 | androidTestCompile 'com.android.support.test:rules:0.5' 55 | androidTestCompile "com.crittercism.dexmaker:dexmaker:1.4" 56 | androidTestCompile "com.crittercism.dexmaker:dexmaker-dx:1.4" 57 | androidTestCompile "com.crittercism.dexmaker:dexmaker-mockito:1.4" 58 | androidTestCompile 'org.mockito:mockito-core:1.10.19' 59 | } 60 | 61 | apply from: 'jacoco.gradle' 62 | apply from: 'bintray.gradle' 63 | -------------------------------------------------------------------------------- /app/gradle.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | POM_NAME=SQLite 3 | POM_ARTIFACT_ID=arturvasilov 4 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /app/jacoco.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'jacoco' 2 | apply plugin: 'com.github.kt3k.coveralls' 3 | 4 | jacoco { 5 | toolVersion = "0.7.6.201602180812" 6 | } 7 | 8 | task jacocoTestReport(type: JacocoReport) { 9 | group = 'Reporting' 10 | description = 'Generate Jacoco coverage reports after running tests.' 11 | reports { 12 | xml { 13 | enabled = true 14 | destination = "${project.buildDir}/reports/jacoco/test/jacocoTestReport.xml" 15 | } 16 | } 17 | jacocoClasspath = project.configurations['androidJacocoAnt'] 18 | sourceDirectories = fileTree(dir: "${project.projectDir}/src/main/java") 19 | classDirectories = fileTree(dir: "${project.buildDir}/intermediates/classes/debug", excludes: [ 20 | '**/R.class', 21 | '**/R$*.class', 22 | '**/BuildConfig.*', 23 | '**/Manifest*.*', 24 | '**/*Test*.*', 25 | '**/*Assert*.*' 26 | ]) 27 | executionData = fileTree(dir: project.buildDir, includes: ['**/*.exec', '**/*.ec']) 28 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/core/ContentProviderErrorsTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | import android.support.annotation.NonNull; 6 | import android.support.test.InstrumentationRegistry; 7 | 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.JUnit4; 12 | import org.sqlite.database.sqlite.SQLiteDatabase; 13 | 14 | /** 15 | * @author Artur Vasilov 16 | */ 17 | @RunWith(JUnit4.class) 18 | public class ContentProviderErrorsTest { 19 | 20 | private static final Table BAD_TABLE = new BadTable(); 21 | 22 | @Before 23 | public void setUp() throws Exception { 24 | SQLite.initialize(InstrumentationRegistry.getContext()); 25 | } 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void testBadQuery() throws Exception { 29 | SQLite.get().query(BAD_TABLE); 30 | } 31 | 32 | @Test(expected = IllegalArgumentException.class) 33 | public void testBadInsert() throws Exception { 34 | SQLite.get().insert(BAD_TABLE, 5); 35 | } 36 | 37 | @Test(expected = IllegalArgumentException.class) 38 | public void testBadUpdate() throws Exception { 39 | SQLite.get().update(BAD_TABLE, Where.create(), 100); 40 | } 41 | 42 | @Test(expected = IllegalArgumentException.class) 43 | public void testBadDelete() throws Exception { 44 | SQLite.get().delete(BAD_TABLE); 45 | } 46 | 47 | private static class BadTable extends BaseTable { 48 | 49 | @Override 50 | public void onCreate(@NonNull SQLiteDatabase database) { 51 | throw new RuntimeException("Stub!"); 52 | } 53 | 54 | @NonNull 55 | @Override 56 | public ContentValues toValues(@NonNull Integer integer) { 57 | return new ContentValues(); 58 | } 59 | 60 | @NonNull 61 | @Override 62 | public Integer fromCursor(@NonNull Cursor cursor) { 63 | throw new RuntimeException("Stub!"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/core/SQLiteTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.support.test.InstrumentationRegistry; 4 | import android.support.test.runner.AndroidJUnit4; 5 | 6 | import junit.framework.Assert; 7 | 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.ArgumentCaptor; 13 | import org.mockito.Mockito; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import ru.arturvasilov.sqlite.testutils.TestObject; 19 | import ru.arturvasilov.sqlite.testutils.TestTable; 20 | 21 | import static junit.framework.TestCase.assertEquals; 22 | import static junit.framework.TestCase.assertTrue; 23 | import static org.junit.Assert.assertNull; 24 | import static org.mockito.Matchers.anyListOf; 25 | import static org.mockito.Mockito.times; 26 | 27 | @RunWith(AndroidJUnit4.class) 28 | public class SQLiteTest { 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | SQLite.initialize(InstrumentationRegistry.getContext()); 33 | SQLite.get().disableAutomaticNotifications(); 34 | } 35 | 36 | @Test 37 | public void testInsertElement() throws Exception { 38 | TestObject test = new TestObject(5, 4.8, "aaaa"); 39 | SQLite.get().insert(TestTable.TABLE, test); 40 | TestObject saved = SQLite.get().querySingle(TestTable.TABLE); 41 | assertEquals(test, saved); 42 | } 43 | 44 | @Test 45 | public void testInsertMultiple() throws Exception { 46 | List elements = new ArrayList<>(); 47 | elements.add(new TestObject(1, 9.5, "a")); 48 | elements.add(new TestObject(2, 6.7, "ab")); 49 | elements.add(new TestObject(3, 8.2, "abc")); 50 | elements.add(new TestObject(4, 3.4, "abcd")); 51 | elements.add(new TestObject(5, 6.5, "abcde")); 52 | SQLite.get().insert(TestTable.TABLE, elements); 53 | 54 | int savedSize = SQLite.get().query(TestTable.TABLE).size(); 55 | assertEquals(elements.size(), savedSize); 56 | } 57 | 58 | @Test 59 | public void testInsertReplacedPrimaryKey() throws Exception { 60 | List elements = new ArrayList<>(); 61 | elements.add(new TestObject(11, 9.9, "a")); 62 | elements.add(new TestObject(12, 8.9, "ab")); 63 | SQLite.get().insert(TestTable.TABLE, elements); 64 | 65 | SQLite.get().insert(TestTable.TABLE, new TestObject(12, 5.7, "bc")); 66 | List savedElements = SQLite.get().query(TestTable.TABLE); 67 | assertEquals(elements.size(), savedElements.size()); 68 | assertEquals("bc", savedElements.get(1).getText()); 69 | } 70 | 71 | @Test 72 | public void testQueryWithSingleObject() throws Exception { 73 | List elements = new ArrayList<>(); 74 | elements.add(new TestObject(11, 1.2, "a")); 75 | elements.add(new TestObject(12, 2.7, "ab")); 76 | SQLite.get().insert(TestTable.TABLE, elements); 77 | 78 | TestObject element = SQLite.get().querySingle(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 12)); 79 | assertEquals(elements.get(1), element); 80 | } 81 | 82 | @Test 83 | public void testQueryNull() throws Exception { 84 | TestObject testObject = SQLite.get().querySingle(TestTable.TABLE); 85 | Assert.assertNull(testObject); 86 | } 87 | 88 | @Test 89 | public void testQueryList() throws Exception { 90 | List elements = new ArrayList<>(); 91 | elements.add(new TestObject(110, 0, "a")); 92 | elements.add(new TestObject(111, 7.8, "ab")); 93 | elements.add(new TestObject(112, 9.6, "xaxka")); 94 | elements.add(new TestObject(113, 5.7, "ab")); 95 | elements.add(new TestObject(114, 6.8, "abc")); 96 | SQLite.get().insert(TestTable.TABLE, elements); 97 | 98 | List savedElements = SQLite.get().query(TestTable.TABLE, Where.create().equalTo(TestTable.TEXT, "ab")); 99 | 100 | assertEquals(2, savedElements.size()); 101 | assertEquals(elements.get(1), savedElements.get(0)); 102 | assertEquals(elements.get(3), savedElements.get(1)); 103 | } 104 | 105 | @Test 106 | public void testEmptyElement() throws Exception { 107 | TestObject element = SQLite.get().querySingle(TestTable.TABLE); 108 | assertNull(element); 109 | } 110 | 111 | @Test 112 | public void testEmptyList() throws Exception { 113 | List elements = SQLite.get().query(TestTable.TABLE); 114 | assertTrue(elements.isEmpty()); 115 | } 116 | 117 | @Test 118 | public void testInsertEmptyList() throws Exception { 119 | int count = SQLite.get().insert(TestTable.TABLE, new ArrayList()); 120 | assertEquals(0, count); 121 | } 122 | 123 | @Test 124 | public void testUpdateRow() throws Exception { 125 | TestObject element = new TestObject(123321, 8.4, "abc"); 126 | SQLite.get().insert(TestTable.TABLE, element); 127 | 128 | TestObject update = new TestObject(123321, 0.6, "xyz"); 129 | int count = SQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 123321), update); 130 | assertEquals(1, count); 131 | 132 | TestObject updated = SQLite.get().querySingle(TestTable.TABLE); 133 | assertEquals(update, updated); 134 | } 135 | 136 | @Test 137 | public void testNoUpdate() throws Exception { 138 | TestObject element = new TestObject(123321, 6, "abc"); 139 | SQLite.get().insert(TestTable.TABLE, element); 140 | 141 | TestObject update = new TestObject(123322, 5.2, "xyz"); 142 | int count = SQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 1233212), update); 143 | assertEquals(0, count); 144 | 145 | TestObject notUpdated = SQLite.get().querySingle(TestTable.TABLE); 146 | assertEquals(element, notUpdated); 147 | } 148 | 149 | @Test 150 | public void testDeleteAll() throws Exception { 151 | List elements = new ArrayList<>(); 152 | elements.add(new TestObject(11, 4, "a")); 153 | elements.add(new TestObject(12, 8.4, "ab")); 154 | SQLite.get().insert(TestTable.TABLE, elements); 155 | 156 | int count = SQLite.get().delete(TestTable.TABLE); 157 | assertEquals(2, count); 158 | 159 | elements = SQLite.get().query(TestTable.TABLE); 160 | assertTrue(elements.isEmpty()); 161 | } 162 | 163 | @Test 164 | public void testDeleteByParameters() throws Exception { 165 | List elements = new ArrayList<>(); 166 | elements.add(new TestObject(110, 7.1, "a")); 167 | elements.add(new TestObject(111, 8, "ab")); 168 | elements.add(new TestObject(112, 9, "xaxka")); 169 | elements.add(new TestObject(113, 6.9, "ab")); 170 | elements.add(new TestObject(114, 2.3, "abc")); 171 | SQLite.get().insert(TestTable.TABLE, elements); 172 | 173 | int count = SQLite.get().delete(TestTable.TABLE, Where.create().equalTo(TestTable.TEXT, "ab")); 174 | assertEquals(2, count); 175 | 176 | List leftElements = SQLite.get().query(TestTable.TABLE); 177 | 178 | assertEquals(3, leftElements.size()); 179 | assertEquals(elements.get(0), leftElements.get(0)); 180 | assertEquals(elements.get(2), leftElements.get(1)); 181 | assertEquals(elements.get(4), leftElements.get(2)); 182 | } 183 | 184 | @Test 185 | public void testSQLite() throws Exception { 186 | TestObject test = new TestObject(1, 9.3, "aaaa"); 187 | SQLite.get().insert(TestTable.TABLE, test); 188 | 189 | List all = SQLite.get().query(TestTable.TABLE); 190 | assertEquals(1, all.size()); 191 | assertTrue(test.equals(all.get(0))); 192 | 193 | test.setText("BBBBB"); 194 | int rows = SQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 1), test); 195 | 196 | all = SQLite.get().query(TestTable.TABLE); 197 | assertEquals(1, rows); 198 | assertEquals(1, all.size()); 199 | assertTrue(test.equals(all.get(0))); 200 | 201 | SQLite.get().delete(TestTable.TABLE); 202 | 203 | assertTrue(SQLite.get().query(TestTable.TABLE).isEmpty()); 204 | } 205 | 206 | @Test 207 | public void testObserveTableChange() throws Exception { 208 | SQLite.get().enableAutomaticNotifications(); 209 | 210 | BasicTableObserver observer = Mockito.mock(BasicTableObserver.class); 211 | Mockito.doNothing().when(observer).onTableChanged(); 212 | SQLite.get().registerObserver(TestTable.TABLE, observer); 213 | 214 | SQLite.get().insert(TestTable.TABLE, new TestObject(5, 6, "text")); 215 | SQLite.get().delete(TestTable.TABLE); 216 | Thread.sleep(300); 217 | 218 | Mockito.verify(observer, times(2)).onTableChanged(); 219 | 220 | Mockito.reset(observer); 221 | SQLite.get().unregisterObserver(observer); 222 | SQLite.get().insert(TestTable.TABLE, new TestObject(5, 6, "text")); 223 | Thread.sleep(300); 224 | Mockito.verifyNoMoreInteractions(observer); 225 | } 226 | 227 | @SuppressWarnings("unchecked") 228 | @Test 229 | public void testObserveTableChangeWithData() throws Exception { 230 | SQLite.get().enableAutomaticNotifications(); 231 | 232 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class); 233 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class)); 234 | SQLite.get().registerObserver(TestTable.TABLE, observer); 235 | 236 | SQLite.get().insert(TestTable.TABLE, new TestObject(5, 9.7, "text")); 237 | Thread.sleep(300); 238 | Mockito.verify(observer).onTableChanged(anyListOf(TestObject.class)); 239 | 240 | Mockito.reset(observer); 241 | SQLite.get().unregisterObserver(observer); 242 | SQLite.get().delete(TestTable.TABLE); 243 | Thread.sleep(300); 244 | Mockito.verifyNoMoreInteractions(observer); 245 | } 246 | 247 | @SuppressWarnings("unchecked") 248 | @Test 249 | public void testObserveTableChangeWithDataAndQuery() throws Exception { 250 | SQLite.get().enableAutomaticNotifications(); 251 | 252 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class); 253 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class)); 254 | SQLite.get().registerObserver(TestTable.TABLE, observer, Where.create().equalTo(TestTable.ID, 5)); 255 | 256 | List list = new ArrayList<>(); 257 | list.add(new TestObject(5, 9.7, "text")); 258 | list.add(new TestObject(6, 8, "text2")); 259 | SQLite.get().insert(TestTable.TABLE, list); 260 | Thread.sleep(300); 261 | 262 | ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); 263 | Mockito.verify(observer).onTableChanged(captor.capture()); 264 | assertEquals(1, captor.getValue().size()); 265 | } 266 | 267 | @SuppressWarnings("unchecked") 268 | @Test 269 | public void testControlAutomaticUpdates() throws Exception { 270 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class); 271 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class)); 272 | SQLite.get().registerObserver(TestTable.TABLE, observer, Where.create().equalTo(TestTable.ID, 100)); 273 | 274 | SQLite.get().insert(TestTable.TABLE, new TestObject(1000, 8.8, "None")); 275 | Thread.sleep(300); 276 | 277 | Mockito.verifyNoMoreInteractions(observer); 278 | 279 | SQLite.get().enableAutomaticNotifications(); 280 | SQLite.get().delete(TestTable.TABLE); 281 | Thread.sleep(300); 282 | 283 | ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); 284 | Mockito.verify(observer).onTableChanged(captor.capture()); 285 | assertEquals(0, captor.getValue().size()); 286 | } 287 | 288 | @SuppressWarnings("unchecked") 289 | @Test 290 | public void testManualNotify() throws Exception { 291 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class); 292 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class)); 293 | SQLite.get().registerObserver(TestTable.TABLE, observer, Where.create().equalTo(TestTable.ID, 1)); 294 | 295 | SQLite.get().insert(TestTable.TABLE, new TestObject(1, 3.2, "hello")); 296 | SQLite.get().notifyTableChanged(TestTable.TABLE); 297 | Thread.sleep(300); 298 | Mockito.verify(observer).onTableChanged(anyListOf(TestObject.class)); 299 | } 300 | 301 | @After 302 | public void tearDown() throws Exception { 303 | SQLite.get().delete(TestTable.TABLE); 304 | } 305 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/rx/RxSQLiteTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.rx; 2 | 3 | import android.support.test.InstrumentationRegistry; 4 | import android.support.test.runner.AndroidJUnit4; 5 | 6 | import org.junit.After; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.ArgumentCaptor; 12 | import org.mockito.Mockito; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import io.reactivex.Observable; 18 | import io.reactivex.disposables.Disposable; 19 | import io.reactivex.functions.BiFunction; 20 | import io.reactivex.functions.Consumer; 21 | import ru.arturvasilov.sqlite.core.SQLite; 22 | import ru.arturvasilov.sqlite.core.Where; 23 | import ru.arturvasilov.sqlite.testutils.RxUtils; 24 | import ru.arturvasilov.sqlite.testutils.TestObject; 25 | import ru.arturvasilov.sqlite.testutils.TestTable; 26 | 27 | import static org.junit.Assert.assertEquals; 28 | import static org.junit.Assert.assertNotNull; 29 | import static org.junit.Assert.fail; 30 | import static org.mockito.Matchers.anyBoolean; 31 | import static org.mockito.Matchers.anyListOf; 32 | 33 | /** 34 | * @author Artur Vasilov 35 | */ 36 | @RunWith(AndroidJUnit4.class) 37 | public class RxSQLiteTest { 38 | 39 | @Before 40 | public void setUp() throws Exception { 41 | SQLite.initialize(InstrumentationRegistry.getContext()); 42 | RxUtils.setupTestSchedulers(); 43 | SQLite.get().disableAutomaticNotifications(); 44 | } 45 | 46 | @Test 47 | public void testSingleElement() throws Exception { 48 | TestObject testElement = new TestObject(10, 10, "abc"); 49 | SQLite.get().insert(TestTable.TABLE, testElement); 50 | 51 | TestObject savedElement = RxSQLite.get() 52 | .querySingle(TestTable.TABLE) 53 | .test() 54 | .values() 55 | .get(0); 56 | 57 | Assert.assertEquals(testElement, savedElement); 58 | } 59 | 60 | @Test 61 | public void testElementsList() throws Exception { 62 | List elements = new ArrayList<>(); 63 | elements.add(new TestObject(1, 9.5, "a")); 64 | elements.add(new TestObject(2, 6.7, "ab")); 65 | elements.add(new TestObject(3, 8.2, "abc")); 66 | elements.add(new TestObject(4, 3.4, "abcd")); 67 | elements.add(new TestObject(5, 6.5, "abcde")); 68 | SQLite.get().insert(TestTable.TABLE, elements); 69 | 70 | Observable.zip(RxSQLite.get().query(TestTable.TABLE), 71 | Observable.just(elements), new BiFunction, List, Object>() { 72 | @Override 73 | public Object apply(List testElements, List savedElements) throws Exception { 74 | assertEquals(testElements.size(), savedElements.size()); 75 | for (int i = 0; i < testElements.size(); i++) { 76 | assertEquals(testElements.size(), savedElements.size()); 77 | } 78 | return null; 79 | } 80 | }) 81 | .test(); 82 | } 83 | 84 | @Test 85 | public void testQueryWithParameters() throws Exception { 86 | List elements = new ArrayList<>(); 87 | elements.add(new TestObject(1, 9.5, "a")); 88 | elements.add(new TestObject(2, 6.7, "ab")); 89 | elements.add(new TestObject(3, 8.2, "abc")); 90 | SQLite.get().insert(TestTable.TABLE, elements); 91 | 92 | int count = RxSQLite.get().query(TestTable.TABLE, Where.create().lessThanOrEqualTo(TestTable.ID, 2)) 93 | .test() 94 | .values() 95 | .get(0) 96 | .size(); 97 | 98 | assertEquals(2, count); 99 | } 100 | 101 | @Test 102 | public void testEmptyTable() throws Exception { 103 | RxSQLite.get().querySingle(TestTable.TABLE) 104 | .subscribe(new Consumer() { 105 | @Override 106 | public void accept(TestObject testObject) { 107 | fail(); 108 | } 109 | }, new Consumer() { 110 | @Override 111 | public void accept(Throwable throwable) { 112 | fail(); 113 | } 114 | }); 115 | } 116 | 117 | @Test 118 | public void testInsertElement() throws Exception { 119 | assertNotNull(RxSQLite.get().insert(TestTable.TABLE, new TestObject(100, 7, "100")).test().values().get(0)); 120 | } 121 | 122 | @Test 123 | public void testInsertList() throws Exception { 124 | List elements = new ArrayList<>(); 125 | elements.add(new TestObject(1, 9.5, "a")); 126 | elements.add(new TestObject(2, 6.7, "ab")); 127 | int count = RxSQLite.get().insert(TestTable.TABLE, elements).test().values().get(0); 128 | assertEquals(2, count); 129 | } 130 | 131 | @Test 132 | public void testUpdateElement() throws Exception { 133 | List elements = new ArrayList<>(); 134 | elements.add(new TestObject(1, 9.5, "a")); 135 | elements.add(new TestObject(2, 6.7, "ab")); 136 | SQLite.get().insert(TestTable.TABLE, elements); 137 | 138 | int count = RxSQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 2), new TestObject(2, 6.7, "abc")).test().values().get(0); 139 | assertEquals(1, count); 140 | 141 | RxSQLite.get().querySingle(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 2)) 142 | .subscribe(new Consumer() { 143 | @Override 144 | public void accept(TestObject testObject) { 145 | Assert.assertEquals("abc", testObject.getText()); 146 | } 147 | }, new Consumer() { 148 | @Override 149 | public void accept(Throwable throwable) { 150 | fail(); 151 | } 152 | }); 153 | } 154 | 155 | @Test 156 | public void testDeleteElement() throws Exception { 157 | List elements = new ArrayList<>(); 158 | elements.add(new TestObject(1, 9.5, "a")); 159 | elements.add(new TestObject(2, 6.7, "ab")); 160 | SQLite.get().insert(TestTable.TABLE, elements); 161 | 162 | int count = RxSQLite.get().delete(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 2)).test().values().get(0); 163 | assertEquals(1, count); 164 | } 165 | 166 | @Test 167 | public void testClearTable() throws Exception { 168 | List elements = new ArrayList<>(); 169 | elements.add(new TestObject(1, 9.5, "a")); 170 | elements.add(new TestObject(2, 6.7, "ab")); 171 | elements.add(new TestObject(3, 8.2, "abc")); 172 | SQLite.get().insert(TestTable.TABLE, elements); 173 | 174 | int count = RxSQLite.get().delete(TestTable.TABLE).test().values().get(0); 175 | assertEquals(3, count); 176 | } 177 | 178 | @SuppressWarnings("unchecked") 179 | @Test 180 | public void testObserveTableChange() throws Exception { 181 | SQLite.get().enableAutomaticNotifications(); 182 | 183 | Consumer action = Mockito.mock(Consumer.class); 184 | Mockito.doNothing().when(action).accept(anyBoolean()); 185 | Disposable disposable = RxSQLite.get().observeChanges(TestTable.TABLE).subscribe(action); 186 | 187 | SQLite.get().insert(TestTable.TABLE, new TestObject(10010, 6.4, "changes")); 188 | Thread.sleep(300); 189 | Mockito.verify(action).accept(true); 190 | 191 | Mockito.reset(action); 192 | disposable.dispose(); 193 | 194 | SQLite.get().delete(TestTable.TABLE); 195 | Thread.sleep(300); 196 | Mockito.verifyNoMoreInteractions(action); 197 | } 198 | 199 | @SuppressWarnings("unchecked") 200 | @Test 201 | public void testObserveTableChangeWithData() throws Exception { 202 | SQLite.get().enableAutomaticNotifications(); 203 | 204 | Consumer> action = Mockito.mock(Consumer.class); 205 | Mockito.doNothing().when(action).accept(anyListOf(TestObject.class)); 206 | Disposable disposable = RxSQLite.get().observeChanges(TestTable.TABLE).withQuery().subscribe(action); 207 | 208 | SQLite.get().insert(TestTable.TABLE, new TestObject(10410, 8.9, "ca'pcj;s;vhjvksf;bgd")); 209 | Thread.sleep(300); 210 | Mockito.verify(action).accept(anyListOf(TestObject.class)); 211 | 212 | Mockito.reset(action); 213 | disposable.dispose(); 214 | 215 | SQLite.get().delete(TestTable.TABLE); 216 | Thread.sleep(300); 217 | Mockito.verifyNoMoreInteractions(action); 218 | } 219 | 220 | @SuppressWarnings("unchecked") 221 | @Test 222 | public void testObserveTableChangeWithDataAndQuery() throws Exception { 223 | SQLite.get().enableAutomaticNotifications(); 224 | 225 | Consumer> action = Mockito.mock(Consumer.class); 226 | Mockito.doNothing().when(action).accept(anyListOf(TestObject.class)); 227 | RxSQLite.get().observeChanges(TestTable.TABLE) 228 | .withQuery(Where.create().lessThan(TestTable.RATING, 5)) 229 | .subscribe(action); 230 | 231 | List list = new ArrayList<>(); 232 | list.add(new TestObject(513, 1.6, "text44")); 233 | list.add(new TestObject(8, 7.6, "text2")); 234 | list.add(new TestObject(9, 4, "tex7")); 235 | SQLite.get().insert(TestTable.TABLE, list); 236 | Thread.sleep(300); 237 | 238 | ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); 239 | Mockito.verify(action).accept(captor.capture()); 240 | assertEquals(2, captor.getValue().size()); 241 | } 242 | 243 | @After 244 | public void tearDown() throws Exception { 245 | SQLite.get().delete(TestTable.TABLE); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/testutils/RxUtils.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.testutils; 2 | 3 | import io.reactivex.Scheduler; 4 | import io.reactivex.functions.Function; 5 | import io.reactivex.plugins.RxJavaPlugins; 6 | import io.reactivex.schedulers.Schedulers; 7 | 8 | /** 9 | * @author Artur Vasilov 10 | */ 11 | public class RxUtils { 12 | 13 | public static void setupTestSchedulers() { 14 | try { 15 | RxJavaPlugins.setIoSchedulerHandler(new Function() { 16 | @Override 17 | public Scheduler apply(Scheduler scheduler) { 18 | return Schedulers.trampoline(); 19 | } 20 | }); 21 | } catch (Exception ignored) { 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/testutils/SQLiteProvider.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.testutils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import ru.arturvasilov.sqlite.core.SQLiteConfig; 6 | import ru.arturvasilov.sqlite.core.SQLiteContentProvider; 7 | import ru.arturvasilov.sqlite.core.SQLiteSchema; 8 | 9 | /** 10 | * @author Artur Vasilov 11 | */ 12 | public class SQLiteProvider extends SQLiteContentProvider { 13 | 14 | @Override 15 | protected void prepareConfig(@NonNull SQLiteConfig config) { 16 | config.setAuthority("ru.arturvasilov.sqlite"); 17 | config.setDatabaseName("database.db"); 18 | } 19 | 20 | @Override 21 | protected void prepareSchema(@NonNull SQLiteSchema schema) { 22 | schema.register(TestTable.TABLE); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/testutils/TestObject.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.testutils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | /** 6 | * @author Artur Vasilov 7 | */ 8 | public class TestObject { 9 | 10 | private final int mId; 11 | private final double mRating; 12 | private String mText; 13 | 14 | public TestObject(int id, double rating, @NonNull String text) { 15 | mId = id; 16 | mRating = rating; 17 | mText = text; 18 | } 19 | 20 | public int getId() { 21 | return mId; 22 | } 23 | 24 | public double getRating() { 25 | return mRating; 26 | } 27 | 28 | @NonNull 29 | public String getText() { 30 | return mText; 31 | } 32 | 33 | public void setText(@NonNull String text) { 34 | mText = text; 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | if (this == o) return true; 40 | if (o == null || getClass() != o.getClass()) return false; 41 | 42 | TestObject that = (TestObject) o; 43 | 44 | return getId() == that.getId() && Math.abs(getRating() - that.getRating()) < 0.000001 45 | && getText().equals(that.getText()); 46 | 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = getId(); 52 | result = 31 * result + getText().hashCode(); 53 | return result; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/testutils/TestTable.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.testutils; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | import android.support.annotation.NonNull; 6 | 7 | import org.sqlite.database.sqlite.SQLiteDatabase; 8 | 9 | import ru.arturvasilov.sqlite.core.BaseTable; 10 | import ru.arturvasilov.sqlite.core.Table; 11 | import ru.arturvasilov.sqlite.utils.TableBuilder; 12 | 13 | /** 14 | * @author Artur Vasilov 15 | */ 16 | public class TestTable extends BaseTable { 17 | 18 | public static final Table TABLE = new TestTable(); 19 | 20 | public static final String ID = "id"; 21 | public static final String RATING = "rating"; 22 | public static final String TEXT = "text"; 23 | 24 | @Override 25 | public void onCreate(@NonNull SQLiteDatabase database) { 26 | TableBuilder.create(this) 27 | .intColumn(ID) 28 | .realColumn(RATING) 29 | .textColumn(TEXT) 30 | .primaryKey(ID) 31 | .execute(database); 32 | } 33 | 34 | @NonNull 35 | @Override 36 | public ContentValues toValues(@NonNull TestObject testObject) { 37 | ContentValues values = new ContentValues(); 38 | values.put(ID, testObject.getId()); 39 | values.put(RATING, testObject.getRating()); 40 | values.put(TEXT, testObject.getText()); 41 | return values; 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public TestObject fromCursor(@NonNull Cursor cursor) { 47 | int id = cursor.getInt(cursor.getColumnIndex(ID)); 48 | double rating = cursor.getDouble(cursor.getColumnIndex(RATING)); 49 | String text = cursor.getString(cursor.getColumnIndex(TEXT)); 50 | return new TestObject(id, rating, text); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/arturvasilov/sqlite/utils/EmptyTableTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.utils; 2 | 3 | import android.support.test.InstrumentationRegistry; 4 | import android.support.test.runner.AndroidJUnit4; 5 | 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import ru.arturvasilov.sqlite.core.SQLite; 15 | import ru.arturvasilov.sqlite.testutils.TestObject; 16 | import ru.arturvasilov.sqlite.testutils.TestTable; 17 | 18 | import static org.junit.Assert.assertFalse; 19 | import static org.junit.Assert.assertTrue; 20 | 21 | /** 22 | * @author Artur Vasilov 23 | */ 24 | @RunWith(AndroidJUnit4.class) 25 | public class EmptyTableTest { 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | SQLite.initialize(InstrumentationRegistry.getContext()); 30 | SQLite.get().disableAutomaticNotifications(); 31 | } 32 | 33 | @Test 34 | public void testEmptyTable() throws Exception { 35 | SQLite.get().delete(TestTable.TABLE); 36 | boolean isEmpty = SQLiteUtils.isTableEmpty(TestTable.TABLE); 37 | assertTrue(isEmpty); 38 | } 39 | 40 | @Test 41 | public void testNotEmptyTable() throws Exception { 42 | SQLite.get().insert(TestTable.TABLE, new TestObject(1, 6.4, "test")); 43 | boolean isEmpty = SQLiteUtils.isTableEmpty(TestTable.TABLE); 44 | assertFalse(isEmpty); 45 | } 46 | 47 | @Test 48 | public void testTableChanges() throws Exception { 49 | SQLite.get().insert(TestTable.TABLE, new TestObject(7, 9.1, "testing empty table")); 50 | boolean isEmpty = SQLiteUtils.isTableEmpty(TestTable.TABLE); 51 | assertFalse(isEmpty); 52 | 53 | SQLite.get().delete(TestTable.TABLE); 54 | isEmpty = SQLiteUtils.isTableEmpty(TestTable.TABLE); 55 | assertTrue(isEmpty); 56 | 57 | List list = new ArrayList<>(); 58 | list.add(new TestObject(8, 7.6, "")); 59 | list.add(new TestObject(9, 4, "any")); 60 | SQLite.get().insert(TestTable.TABLE, list); 61 | 62 | isEmpty = SQLiteUtils.isTableEmpty(TestTable.TABLE); 63 | assertFalse(isEmpty); 64 | 65 | SQLite.get().delete(TestTable.TABLE); 66 | isEmpty = SQLiteUtils.isTableEmpty(TestTable.TABLE); 67 | assertTrue(isEmpty); 68 | } 69 | 70 | @After 71 | public void tearDown() throws Exception { 72 | SQLite.get().delete(TestTable.TABLE); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/BaseTable.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.net.Uri; 4 | import android.support.annotation.NonNull; 5 | 6 | import org.sqlite.database.sqlite.SQLiteDatabase; 7 | 8 | /** 9 | * @author Artur Vasilov 10 | */ 11 | public abstract class BaseTable implements Table { 12 | 13 | @NonNull 14 | @Override 15 | public final Uri getUri() { 16 | return SQLiteContentProvider.getBaseUri().buildUpon().appendPath(getTableName()).build(); 17 | } 18 | 19 | @Override 20 | public int getLastUpgradeVersion() { 21 | return 1; 22 | } 23 | 24 | @NonNull 25 | @Override 26 | public String getTableName() { 27 | return getClass().getSimpleName(); 28 | } 29 | 30 | @Override 31 | public void onUpgrade(@NonNull SQLiteDatabase database) { 32 | database.execSQL("DROP TABLE IF EXISTS " + getTableName()); 33 | onCreate(database); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/BasicTableObserver.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | /** 4 | * Use this interface when you want to be notified about the changes in certain table 5 | * This interface is used in {@link SQLite#registerObserver(Table, BasicTableObserver)} 6 | * 7 | * @author Artur Vasilov 8 | */ 9 | public interface BasicTableObserver { 10 | 11 | /** 12 | * This method simply notifies you that there are changes in the database 13 | * (after insert, update and delete operations, which has influenced the table) 14 | */ 15 | void onTableChanged(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/ContentTableObserver.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Use this interface when you want to be notified about the changes in certain table and also read all data from the table 9 | * This interface is used in {@link SQLite#registerObserver(Table, ContentTableObserver)} 10 | * 11 | * @author Artur Vasilov 12 | */ 13 | public interface ContentTableObserver { 14 | 15 | /** 16 | * This method simply notifies you that there are changes in the database 17 | * (after insert, update and delete operations, which has influenced the table). 18 | * 19 | * Data from the table is queried in the background thread, but you must be sure, 20 | * that your table isn't changing too frequently or you may have performance issues. 21 | * 22 | * @param tableData - all elements from the table 23 | */ 24 | void onTableChanged(@NonNull List tableData); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/DatabaseObserver.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.database.ContentObserver; 4 | 5 | /** 6 | * @author Artur Vasilov 7 | */ 8 | class DatabaseObserver extends ContentObserver { 9 | 10 | public DatabaseObserver() { 11 | super(MainHandler.getHandler()); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/MainHandler.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.support.annotation.NonNull; 6 | 7 | /** 8 | * @author Artur Vasilov 9 | */ 10 | class MainHandler { 11 | 12 | private static Handler sHandler; 13 | 14 | private MainHandler() { 15 | } 16 | 17 | @NonNull 18 | public static Handler getHandler() { 19 | Handler handler = sHandler; 20 | if (handler == null) { 21 | synchronized (MainHandler.class) { 22 | handler = sHandler; 23 | if (handler == null) { 24 | handler = sHandler = new Handler(Looper.getMainLooper()); 25 | } 26 | } 27 | } 28 | return handler; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/Observers.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.Context; 4 | import android.database.ContentObserver; 5 | import android.support.annotation.NonNull; 6 | import android.support.v4.util.Pair; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import io.reactivex.android.schedulers.AndroidSchedulers; 12 | import io.reactivex.functions.Consumer; 13 | import io.reactivex.schedulers.Schedulers; 14 | import ru.arturvasilov.sqlite.rx.RxSQLite; 15 | 16 | /** 17 | * @author Artur Vasilov 18 | */ 19 | final class Observers { 20 | 21 | private final List> mObservers = new ArrayList<>(); 22 | 23 | public void registerObserver(@NonNull Context context, @NonNull Table table, 24 | @NonNull final BasicTableObserver observer) { 25 | ContentObserver contentObserver = new DatabaseObserver() { 26 | @Override 27 | public void onChange(boolean selfChange) { 28 | super.onChange(selfChange); 29 | MainHandler.getHandler().post(new Runnable() { 30 | @Override 31 | public void run() { 32 | observer.onTableChanged(); 33 | } 34 | }); 35 | } 36 | }; 37 | context.getContentResolver().registerContentObserver(table.getUri(), false, contentObserver); 38 | mObservers.add(new Pair(observer, contentObserver)); 39 | } 40 | 41 | public void registerObserver(@NonNull Context context, @NonNull final Table table, 42 | @NonNull final ContentTableObserver observer, @NonNull final Where where) { 43 | ContentObserver contentObserver = new DatabaseObserver() { 44 | @Override 45 | public void onChange(boolean selfChange) { 46 | super.onChange(selfChange); 47 | RxSQLite.get().query(table, where) 48 | .subscribeOn(Schedulers.io()) 49 | .observeOn(AndroidSchedulers.mainThread()) 50 | .subscribe(new Consumer>() { 51 | @Override 52 | public void accept(List list) throws Exception { 53 | observer.onTableChanged(list); 54 | } 55 | }); 56 | } 57 | }; 58 | context.getContentResolver().registerContentObserver(table.getUri(), false, contentObserver); 59 | mObservers.add(new Pair(observer, contentObserver)); 60 | } 61 | 62 | public void unregisterObserver(@NonNull Context context, @NonNull BasicTableObserver observer) { 63 | unregisterObserver(context, (Object) observer); 64 | } 65 | 66 | public void unregisterObserver(@NonNull Context context, @NonNull ContentTableObserver observer) { 67 | unregisterObserver(context, (Object) observer); 68 | } 69 | 70 | private void unregisterObserver(@NonNull Context context, @NonNull Object object) { 71 | int index = -1; 72 | for (int i = 0; i < mObservers.size(); i++) { 73 | if (mObservers.get(i).first == object) { 74 | index = i; 75 | } 76 | } 77 | 78 | if (index >= 0) { 79 | context.getContentResolver().unregisterContentObserver(mObservers.get(index).second); 80 | mObservers.remove(index); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/SQLite.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.database.ContentObserver; 7 | import android.database.Cursor; 8 | import android.net.Uri; 9 | import android.support.annotation.NonNull; 10 | import android.support.annotation.Nullable; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import ru.arturvasilov.sqlite.utils.SQLiteUtils; 16 | 17 | /** 18 | * @author Artur Vasilov 19 | */ 20 | public class SQLite { 21 | 22 | static { 23 | System.loadLibrary("sqliteX"); 24 | } 25 | 26 | private static SQLite sSQLite; 27 | 28 | private final Context mContext; 29 | 30 | private final Observers mObservers; 31 | 32 | private boolean mIsAutomaticNotificationsEnabled; 33 | 34 | private SQLite(@NonNull Context context) { 35 | mContext = context; 36 | mObservers = new Observers(); 37 | mIsAutomaticNotificationsEnabled = false; 38 | } 39 | 40 | /** 41 | * This method creates singleton instance of the SQLite and allows you to use non-parametrized method 42 | * {@link SQLite#get()} at any place of your app. 43 | *

44 | * Typically you will call this method in your {@link android.app.Application} class like this: 45 | *

 46 |      * {@code
 47 |      * public class MyApplication extends Application {
 48 |      *  @Override
 49 |      *  public void onCreate() {
 50 |      *      super.onCreate();
 51 |      *      SQLite.initialize(this);
 52 |      *  }
 53 |      * }
 54 |      * 
55 | * 56 | * @param context - any context to access the content provider 57 | * @return created singleton instance of SQLite 58 | */ 59 | @NonNull 60 | public static SQLite initialize(@NonNull Context context) { 61 | SQLite sqLite = sSQLite; 62 | if (sqLite == null) { 63 | synchronized (SQLite.class) { 64 | sqLite = sSQLite; 65 | if (sqLite == null) { 66 | sqLite = sSQLite = new SQLite(context.getApplicationContext()); 67 | } 68 | } 69 | } 70 | return sqLite; 71 | } 72 | 73 | /** 74 | * You must be sure that you've initialized SQLite instance by {@link SQLite#initialize(Context)} 75 | * or {@link IllegalStateException} will be thrown. 76 | * 77 | * @return singleton instance of SQLite 78 | */ 79 | @NonNull 80 | public static SQLite get() { 81 | if (sSQLite == null) { 82 | throw new IllegalStateException("You should call initialize(Context) first, to initialize the database"); 83 | } 84 | return sSQLite; 85 | } 86 | 87 | /** 88 | * This methods returns all rows of the table. 89 | * If you want to specify query parameters, you should call {@link SQLite#query(Table, Where)} 90 | * 91 | * @param table - table you want to query 92 | * @return all rows from the table as a list of table class objects. 93 | */ 94 | @NonNull 95 | public List query(@NonNull Table table) { 96 | return query(table, Where.create()); 97 | } 98 | 99 | /** 100 | * @param table - table you want to query 101 | * @param where - arguments for query 102 | * @return all rows from the table which satisfy where parameter as a list of table class objects. 103 | */ 104 | @NonNull 105 | public List query(@NonNull Table table, @NonNull Where where) { 106 | List list = new ArrayList<>(); 107 | 108 | Cursor cursor = mContext.getContentResolver().query(table.getUri(), null, where.where(), where.whereArgs(), null); 109 | try { 110 | if (SQLiteUtils.isEmptyCursor(cursor)) { 111 | return list; 112 | } 113 | do { 114 | T t = table.fromCursor(cursor); 115 | list.add(t); 116 | } while (cursor.moveToNext()); 117 | return list; 118 | } finally { 119 | SQLiteUtils.safeCloseCursor(cursor); 120 | } 121 | } 122 | 123 | /** 124 | * Query for the first object in the table 125 | * 126 | * @param table - table you want to query 127 | * @return first object from the table or null if table is empty 128 | */ 129 | @Nullable 130 | public T querySingle(@NonNull Table table) { 131 | return querySingle(table, Where.create()); 132 | } 133 | 134 | /** 135 | * Query for the first object in the table which satisfy where parameter 136 | * 137 | * @param table - table you want to query 138 | * @param where - arguments for query 139 | * @return first object from the table or null if table is empty 140 | */ 141 | @Nullable 142 | public T querySingle(@NonNull Table table, @NonNull Where where) { 143 | Cursor cursor = mContext.getContentResolver().query(table.getUri(), null, where.where(), where.whereArgs(), where.limit()); 144 | try { 145 | if (SQLiteUtils.isEmptyCursor(cursor)) { 146 | return null; 147 | } 148 | return table.fromCursor(cursor); 149 | } finally { 150 | SQLiteUtils.safeCloseCursor(cursor); 151 | } 152 | } 153 | 154 | /** 155 | * This method inserts object to the table. In cases of conflict the old object will be replaced by the new one. 156 | * 157 | * @param table - table in which you want to insert object 158 | * @param object - object to insert in database 159 | * @return uri of inserted object. In most cases you won't use it. 160 | */ 161 | @Nullable 162 | public Uri insert(@NonNull Table table, @NonNull T object) { 163 | Uri uri = mContext.getContentResolver().insert(table.getUri(), table.toValues(object)); 164 | if (uri != null && mIsAutomaticNotificationsEnabled) { 165 | notifyTableChanged(table); 166 | } 167 | return uri; 168 | } 169 | 170 | /** 171 | * This method inserts objects to the table. In cases of conflict the old objects will be replaced by the new ones. 172 | * 173 | * @param table - table in which you want to insert objects 174 | * @param objects - list of objects to insert in database 175 | * @return count of successfully inserted objects 176 | */ 177 | public int insert(@NonNull Table table, @NonNull List objects) { 178 | ContentValues[] values = new ContentValues[objects.size()]; 179 | for (int i = 0; i < objects.size(); i++) { 180 | values[i] = table.toValues(objects.get(i)); 181 | } 182 | int count = mContext.getContentResolver().bulkInsert(table.getUri(), values); 183 | if (count > 0 && mIsAutomaticNotificationsEnabled) { 184 | notifyTableChanged(table); 185 | } 186 | return count; 187 | } 188 | 189 | /** 190 | * This method clears the table passed as a parameters 191 | * If you want to specify parameters for deleting, you should call {@link SQLite#delete(Table, Where)} 192 | * 193 | * @param table - table you want to clear 194 | * @return count of deleted rows 195 | */ 196 | public int delete(@NonNull Table table) { 197 | return delete(table, Where.create()); 198 | } 199 | 200 | /** 201 | * This method deletes all rows in the table which satisfy where parameter 202 | * 203 | * @param table - table from which you want to delete rows 204 | * @param where - arguments for delete rows from the table 205 | * @return count of deleted objects 206 | */ 207 | public int delete(@NonNull Table table, @NonNull Where where) { 208 | int count = mContext.getContentResolver().delete(table.getUri(), where.where(), where.whereArgs()); 209 | if (count > 0 && mIsAutomaticNotificationsEnabled) { 210 | notifyTableChanged(table); 211 | } 212 | return count; 213 | } 214 | 215 | /** 216 | * This method updates all rows in the table which satisfy where parameter 217 | * 218 | * @param table - table where you want to update rows 219 | * @param where - arguments for update rows in the table 220 | * @param newObject - object which will replace all rows which satisfy where parameter 221 | * @return count of updated objects 222 | */ 223 | public int update(@NonNull Table table, @NonNull Where where, @NonNull T newObject) { 224 | int count = mContext.getContentResolver().update(table.getUri(), table.toValues(newObject), 225 | where.where(), where.whereArgs()); 226 | if (count > 0 && mIsAutomaticNotificationsEnabled) { 227 | notifyTableChanged(table); 228 | } 229 | return count; 230 | } 231 | 232 | /** 233 | * Attaches callback to get notified about changes in certain table 234 | * For more information take a look at {@link BasicTableObserver} 235 | * 236 | * @param table - table to observe changes in 237 | * @param observer - listener which will be called when table changes 238 | */ 239 | public void registerObserver(@NonNull Table table, @NonNull final BasicTableObserver observer) { 240 | mObservers.registerObserver(mContext, table, observer); 241 | } 242 | 243 | /** 244 | * Attaches callback to get notified about changes in certain table and query all rows 245 | * For more information take a look at {@link ContentTableObserver} 246 | *

247 | * {@link SQLite#registerObserver(Table, ContentTableObserver, Where)} 248 | * 249 | * @param table - table to observe changes in 250 | * @param observer - listener which will be called when table changes 251 | */ 252 | public void registerObserver(@NonNull Table table, @NonNull final ContentTableObserver observer) { 253 | mObservers.registerObserver(mContext, table, observer, Where.create()); 254 | } 255 | 256 | /** 257 | * Attaches callback to get notified about changes in certain table and query rows which satisfies where parameter 258 | * For more information take a look at {@link ContentTableObserver} 259 | * 260 | * @param table - table to observe changes in 261 | * @param observer - listener which will be called when table changes 262 | * @param where - arguments for query 263 | */ 264 | public void registerObserver(@NonNull Table table, @NonNull ContentTableObserver observer, @NonNull Where where) { 265 | mObservers.registerObserver(mContext, table, observer, where); 266 | } 267 | 268 | /** 269 | * Detaches listener from observing changes in database 270 | * 271 | * @param observer - listener to detach from ContentProvider notifications 272 | */ 273 | public void unregisterObserver(@NonNull BasicTableObserver observer) { 274 | mObservers.unregisterObserver(mContext, observer); 275 | } 276 | 277 | /** 278 | * Detaches listener from observing changes in database 279 | * 280 | * @param observer - listener to detach from ContentProvider notifications 281 | */ 282 | public void unregisterObserver(@NonNull ContentTableObserver observer) { 283 | mObservers.unregisterObserver(mContext, observer); 284 | } 285 | 286 | /** 287 | * Enables {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver)} 288 | * automatic calls when table changes from SQLite methods. 289 | *

290 | * By default automatic notifications are disabled to let you have more control on them. 291 | */ 292 | public void enableAutomaticNotifications() { 293 | mIsAutomaticNotificationsEnabled = true; 294 | } 295 | 296 | /** 297 | * Disables {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver)} 298 | * automatic calls when table changes from SQLite methods. 299 | *

300 | * By default automatic notifications are disabled. 301 | */ 302 | public void disableAutomaticNotifications() { 303 | mIsAutomaticNotificationsEnabled = false; 304 | } 305 | 306 | /** 307 | * Notifies all observers about table change. This call will invoke notifications for all observers, 308 | * which are registered using one of the register method: 309 | * {@link SQLite#registerObserver(Table, BasicTableObserver)} 310 | * {@link SQLite#registerObserver(Table, ContentTableObserver)} 311 | * {@link SQLite#registerObserver(Table, ContentTableObserver, Where)} 312 | * 313 | * @param table - uri from this table will be used for notification for observers 314 | */ 315 | public void notifyTableChanged(@NonNull Table table) { 316 | mContext.getContentResolver().notifyChange(table.getUri(), null); 317 | } 318 | 319 | /** 320 | * Returns the instance of ContentResolver, which is used for all operations in {@link SQLite} class 321 | * 322 | * You may use it to work with data directly, but you should be careful, since you can loose 323 | * features like automatic notifications about table changes. 324 | * 325 | * @return instance of ContentResolver which is associated with {@link SQLiteContentProvider} 326 | */ 327 | @NonNull 328 | public ContentResolver getContentResolver() { 329 | return mContext.getContentResolver(); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/SQLiteConfig.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.support.annotation.NonNull; 6 | 7 | /** 8 | * Configuration class for SQLite database 9 | * 10 | * @author Artur Vasilov 11 | */ 12 | public class SQLiteConfig { 13 | 14 | private static final String PREFS_NAME = "sqlite_config_prefs"; 15 | 16 | private static final String DATABASE_NAME_KEY = "database_name"; 17 | private static final String AUTHORITY_KEY = "authority"; 18 | 19 | private static final String DEFAULT_DATABASE_NAME = "ru.sqlite.database.database"; 20 | private static final String DEFAULT_AUTHORITY = "ru.sqlite.database"; 21 | 22 | private final Context mContext; 23 | 24 | private String mDatabaseName; 25 | private String mAuthority; 26 | 27 | SQLiteConfig(Context context) { 28 | mContext = context; 29 | 30 | SharedPreferences prefs = getPrefs(); 31 | mDatabaseName = prefs.getString(DATABASE_NAME_KEY, DEFAULT_DATABASE_NAME); 32 | mAuthority = prefs.getString(AUTHORITY_KEY, DEFAULT_AUTHORITY); 33 | } 34 | 35 | /** 36 | * @param databaseName - name of the app database 37 | */ 38 | public void setDatabaseName(@NonNull String databaseName) { 39 | mDatabaseName = databaseName; 40 | } 41 | 42 | /** 43 | * @param uri - authority of the app database 44 | */ 45 | public void setAuthority(@NonNull String uri) { 46 | mAuthority = uri; 47 | } 48 | 49 | @NonNull 50 | String getDatabaseName() { 51 | return mDatabaseName; 52 | } 53 | 54 | @NonNull 55 | String getAuthority() { 56 | return mAuthority; 57 | } 58 | 59 | @NonNull 60 | private SharedPreferences getPrefs() { 61 | return mContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/SQLiteContentProvider.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.ContentProvider; 4 | import android.content.ContentUris; 5 | import android.content.ContentValues; 6 | import android.database.Cursor; 7 | import android.net.Uri; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.text.TextUtils; 11 | 12 | import org.sqlite.database.sqlite.SQLiteDatabase; 13 | 14 | /** 15 | * This class provides implementation for all operations in ContentProvider 16 | * and based on SQLite database. 17 | * 18 | * You only have to implement two methods {@link SQLiteContentProvider#prepareConfig(SQLiteConfig)} 19 | * and {@link SQLiteContentProvider#prepareSchema(SQLiteSchema)}, the rest is handled by the library. 20 | * 21 | * @author Artur Vasilov 22 | */ 23 | public abstract class SQLiteContentProvider extends ContentProvider { 24 | 25 | private SQLiteSchema mSchema; 26 | 27 | private SQLiteHelper mSQLiteHelper; 28 | 29 | private static String sContentAuthority; 30 | private static Uri sBaseUri; 31 | 32 | /** 33 | * In this method you can specify configuration for your database (for this moment only name and authority) 34 | * 35 | * @param config - configuration for SQLite database 36 | */ 37 | protected abstract void prepareConfig(@NonNull SQLiteConfig config); 38 | 39 | /** 40 | * In this method you must add all tables you want to use in your app. 41 | * To add table call {@link SQLiteSchema#register(Table)} 42 | * 43 | * @param schema - schema for SQLite database 44 | */ 45 | protected abstract void prepareSchema(@NonNull SQLiteSchema schema); 46 | 47 | @Override 48 | public final boolean onCreate() { 49 | SQLiteConfig config = new SQLiteConfig(getContext()); 50 | prepareConfig(config); 51 | 52 | sContentAuthority = config.getAuthority(); 53 | sBaseUri = Uri.parse("content://" + sContentAuthority); 54 | 55 | mSchema = new SQLiteSchema(); 56 | prepareSchema(mSchema); 57 | 58 | mSQLiteHelper = new SQLiteHelper(getContext(), config, mSchema); 59 | return true; 60 | } 61 | 62 | @Nullable 63 | @Override 64 | public final String getType(@NonNull Uri uri) { 65 | return mSchema.findTable(uri); 66 | } 67 | 68 | @Nullable 69 | @Override 70 | public final Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { 71 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase(); 72 | String table = getType(uri); 73 | if (TextUtils.isEmpty(table)) { 74 | throw new IllegalArgumentException("No such table to query"); 75 | } else { 76 | return database.query(table, 77 | projection, 78 | selection, 79 | selectionArgs, 80 | null, 81 | null, 82 | sortOrder); 83 | } 84 | } 85 | 86 | @NonNull 87 | @Override 88 | public final Uri insert(@NonNull Uri uri, ContentValues values) { 89 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase(); 90 | String table = getType(uri); 91 | if (TextUtils.isEmpty(table)) { 92 | throw new IllegalArgumentException("No such table to insert"); 93 | } else { 94 | long id = database.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE); 95 | return ContentUris.withAppendedId(uri, id); 96 | } 97 | } 98 | 99 | @Override 100 | public final int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { 101 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase(); 102 | String table = getType(uri); 103 | if (TextUtils.isEmpty(table)) { 104 | throw new IllegalArgumentException("No such table to insert"); 105 | } else { 106 | int numInserted = 0; 107 | database.beginTransaction(); 108 | try { 109 | for (ContentValues contentValues : values) { 110 | long id = database.insertWithOnConflict(table, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE); 111 | if (id > 0) { 112 | numInserted++; 113 | } 114 | } 115 | database.setTransactionSuccessful(); 116 | } finally { 117 | database.endTransaction(); 118 | } 119 | return numInserted; 120 | } 121 | } 122 | 123 | @Override 124 | public final int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { 125 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase(); 126 | String table = getType(uri); 127 | if (TextUtils.isEmpty(table)) { 128 | throw new IllegalArgumentException("No such table to delete"); 129 | } else { 130 | return database.delete(table, selection, selectionArgs); 131 | } 132 | } 133 | 134 | @Override 135 | public final int update(@NonNull Uri uri, ContentValues values, 136 | String selection, String[] selectionArgs) { 137 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase(); 138 | String table = getType(uri); 139 | if (TextUtils.isEmpty(table)) { 140 | throw new IllegalArgumentException("No such table to update"); 141 | } else { 142 | return database.update(table, values, selection, selectionArgs); 143 | } 144 | } 145 | 146 | @NonNull 147 | static String getContentAuthority() { 148 | return sContentAuthority; 149 | } 150 | 151 | @NonNull 152 | static Uri getBaseUri() { 153 | return sBaseUri; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/SQLiteHelper.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | 6 | import org.sqlite.database.sqlite.SQLiteDatabase; 7 | import org.sqlite.database.sqlite.SQLiteOpenHelper; 8 | 9 | import ru.arturvasilov.sqlite.rx.RxSQLite; 10 | 11 | /** 12 | * @author Artur Vasilov 13 | */ 14 | class SQLiteHelper extends SQLiteOpenHelper { 15 | 16 | private final SQLiteSchema mSchema; 17 | 18 | public SQLiteHelper(Context context, @NonNull SQLiteConfig config, @NonNull SQLiteSchema schema) { 19 | super(context, context.getDatabasePath(config.getDatabaseName()).getPath(), null, schema.calculateVersion()); 20 | mSchema = schema; 21 | context.openOrCreateDatabase(config.getDatabaseName(), 0, null, null); 22 | } 23 | 24 | @Override 25 | public void onCreate(SQLiteDatabase database) { 26 | for (Table table : mSchema) { 27 | table.onCreate(database); 28 | } 29 | } 30 | 31 | @Override 32 | public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { 33 | for (Table table : mSchema) { 34 | if (oldVersion < newVersion && newVersion <= table.getLastUpgradeVersion()) { 35 | table.onUpgrade(database); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/SQLiteSchema.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.UriMatcher; 4 | import android.net.Uri; 5 | import android.support.annotation.NonNull; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | 11 | /** 12 | * Simple class for controlling database schema (for this moment - only tables). 13 | * You have to use only {@link SQLiteSchema#register(Table)} method to add your tables to database. 14 | * 15 | * @author Artur Vasilov 16 | */ 17 | public class SQLiteSchema implements Iterable { 18 | 19 | private final UriMatcher mUriMatcher; 20 | 21 | private final List
mTables; 22 | 23 | SQLiteSchema() { 24 | mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 25 | mTables = new ArrayList<>(); 26 | } 27 | 28 | /** 29 | * This method adds you table to the ContentProvider 30 | * 31 | * @param table - table to add to the database 32 | */ 33 | public void register(@NonNull Table table) { 34 | mUriMatcher.addURI(SQLiteContentProvider.getContentAuthority(), table.getTableName(), mTables.size()); 35 | mTables.add(table); 36 | } 37 | 38 | int calculateVersion() { 39 | int version = 1; 40 | for (Table table : mTables) { 41 | int tableVersion = table.getLastUpgradeVersion(); 42 | if (tableVersion > version) { 43 | version = tableVersion; 44 | } 45 | } 46 | return version; 47 | } 48 | 49 | @NonNull 50 | String findTable(@NonNull Uri uri) { 51 | int index = mUriMatcher.match(uri); 52 | if (index < 0 || index >= mTables.size()) { 53 | return ""; 54 | } 55 | return mTables.get(index).getTableName(); 56 | } 57 | 58 | @Override 59 | public Iterator
iterator() { 60 | return mTables.iterator(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/Table.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | import android.net.Uri; 6 | import android.support.annotation.NonNull; 7 | 8 | import org.sqlite.database.sqlite.SQLiteDatabase; 9 | 10 | /** 11 | * Interface for creating a single table in SQLite database. 12 | * 13 | * You normally won't implement this interface but extend {@link BaseTable}, 14 | * which provides good enough implementations for most of the methods. 15 | * 16 | * @author Artur Vasilov 17 | */ 18 | public interface Table { 19 | 20 | /** 21 | * Each table is registered in ContentProvider with Uri from this method. 22 | * Default implementation returns ContentProvider uri appended with table name. 23 | * 24 | * @return Uri for table in database 25 | */ 26 | @NonNull 27 | Uri getUri(); 28 | 29 | /** 30 | * This method is used for creating and dropping table in database 31 | * and in the default implementation for {@link Table#getUri()} 32 | * 33 | * Default implementation returns simple class name (e.g. UsersTable) 34 | * 35 | * @return name of the table in SQLite database 36 | */ 37 | @NonNull 38 | String getTableName(); 39 | 40 | /** 41 | * This method creates and adds table to the database. 42 | * 43 | * You can create it manually with SQL, but it's recommended to use {@link ru.arturvasilov.sqlite.utils.TableBuilder} 44 | * 45 | * @param database - instance of SQLite database where you should create the table 46 | */ 47 | void onCreate(@NonNull SQLiteDatabase database); 48 | 49 | /** 50 | * This method is provided for data migration purposes. 51 | * 52 | * In this method you should decide how you want to update this table 53 | * (how data migration works - {@link Table#getLastUpgradeVersion()} 54 | * 55 | * Default implementation in {@link BaseTable} simple drops table and call {@link Table#onCreate(SQLiteDatabase)} 56 | * 57 | * @param database - instance of SQLite database where you should update the table 58 | */ 59 | void onUpgrade(@NonNull SQLiteDatabase database); 60 | 61 | /** 62 | * This method is also provided for data migration purposes. 63 | * 64 | * Version of database is calculated as a maximum from all tables in {@link SQLiteSchema}. 65 | * When you have updated the table, you just have to override this method to return maximum value from all tables 66 | * (e.g. current version of database is 5, you overridden this method to return 6 - database version will be six 67 | * and {@link Table#onUpgrade(SQLiteDatabase)} will be called) 68 | * If this method returns value which is less than updated database version, 69 | * {@link Table#onUpgrade(SQLiteDatabase)} won't be called. 70 | * 71 | * @return the version of database where the table was lastly updated 72 | */ 73 | int getLastUpgradeVersion(); 74 | 75 | /** 76 | * @param t - object for this table which you need to convert to {@link ContentValues} to insert in to database 77 | * @return ContentValues instance for object 78 | */ 79 | @NonNull 80 | ContentValues toValues(@NonNull T t); 81 | 82 | /** 83 | * In this method you need to create instance of class for this table from cursor. 84 | * It's guarantee that cursor at the right position and its' state is OK. 85 | * 86 | * @param cursor - cursor (opened and ready to be read) 87 | * @return instance of class for this table 88 | */ 89 | @NonNull 90 | T fromCursor(@NonNull Cursor cursor); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/core/Where.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * Class for constructing SQL queries using Java methods. 12 | * All most popular operators (=, <>, >, <, >=, <=, LIKE, BETWEEN, IN, OR, AND) are supported. 13 | *

14 | * If there is no method that you need, you can always construct query manually with {@link Where#where(String, Object...)}. 15 | *

16 | * Class also supports LIMIT and OFFSET with methods {@link Where#limit(int)} and {@link Where#offset(int)} respectively. 17 | *

18 | * Typical usage of this class looks like this: 19 | *

 20 |  * {code
 21 |  * Where where = Where.create()
 22 |  *       .beginGroup()
 23 |  *           .greaterThanOrEqualTo("age", 18)
 24 |  *           .or()
 25 |  *           .lessThan("age", 50)
 26 |  *       .endGroup()
 27 |  *       .and()
 28 |  *       .like("name", "Sm")
 29 |  *       .limit(1);
 30 |  * }
 31 |  * 
32 | * 33 | * @author Artur Vasilov 34 | */ 35 | public class Where { 36 | 37 | private final StringBuilder mWhereBuilder; 38 | 39 | private final List mBindValues; 40 | 41 | private String mLimit; 42 | private String mOffset; 43 | 44 | private Where() { 45 | mWhereBuilder = new StringBuilder(); 46 | mBindValues = new ArrayList<>(); 47 | } 48 | 49 | @NonNull 50 | public static Where create() { 51 | return new Where(); 52 | } 53 | 54 | @NonNull 55 | public Where where(@NonNull String where, Object... values) { 56 | mWhereBuilder.append(where); 57 | Collections.addAll(mBindValues, values); 58 | return this; 59 | } 60 | 61 | @NonNull 62 | public Where equalTo(@NonNull String column, @NonNull Object value) { 63 | return where(column, " = ?", value); 64 | } 65 | 66 | @NonNull 67 | public Where notEqualTo(@NonNull String column, @NonNull Object value) { 68 | return where(column, " <> ?", value); 69 | } 70 | 71 | @NonNull 72 | public Where lessThan(@NonNull String column, @NonNull Object value) { 73 | return where(column, " < ?", value); 74 | } 75 | 76 | @NonNull 77 | public Where lessThanOrEqualTo(@NonNull String column, @NonNull Object value) { 78 | return where(column, " <= ?", value); 79 | } 80 | 81 | @NonNull 82 | public Where greaterThan(@NonNull String column, @NonNull Object value) { 83 | return where(column, " > ?", value); 84 | } 85 | 86 | @NonNull 87 | public Where greaterThanOrEqualTo(@NonNull String column, @NonNull Object value) { 88 | return where(column, " >= ?", value); 89 | } 90 | 91 | @NonNull 92 | public Where like(@NonNull String column, @NonNull Object value) { 93 | return where(column, " LIKE ?", value); 94 | } 95 | 96 | @NonNull 97 | public Where between(@NonNull String column, @NonNull Object first, @NonNull Object second) { 98 | return where(column, " BETWEEN ? AND ?", first, second); 99 | } 100 | 101 | @NonNull 102 | public Where isNull(@NonNull String column) { 103 | return where(column, " IS NULL"); 104 | } 105 | 106 | @NonNull 107 | public Where notNull(@NonNull String column) { 108 | return where(column, " NOT NULL"); 109 | } 110 | 111 | @NonNull 112 | public Where in(@NonNull String column, @NonNull Object... values) { 113 | final int last = values.length - 1; 114 | mWhereBuilder.append(column).append(" IN("); 115 | for (int i = 0; i < values.length; ++i) { 116 | mWhereBuilder.append("?"); 117 | if (i < last) { 118 | mWhereBuilder.append(", "); 119 | } 120 | } 121 | mWhereBuilder.append(")"); 122 | Collections.addAll(mBindValues, values); 123 | return this; 124 | } 125 | 126 | @NonNull 127 | public Where beginGroup() { 128 | mWhereBuilder.append("("); 129 | return this; 130 | } 131 | 132 | @NonNull 133 | public Where endGroup() { 134 | mWhereBuilder.append(")"); 135 | return this; 136 | } 137 | 138 | @NonNull 139 | public Where and() { 140 | mWhereBuilder.append(" AND "); 141 | return this; 142 | } 143 | 144 | @NonNull 145 | public Where or() { 146 | mWhereBuilder.append(" OR "); 147 | return this; 148 | } 149 | 150 | @NonNull 151 | public Where limit(int limit) { 152 | mLimit = String.valueOf(limit); 153 | return this; 154 | } 155 | 156 | @NonNull 157 | public Where offset(int offset) { 158 | mOffset = String.valueOf(offset); 159 | return this; 160 | } 161 | 162 | @Nullable 163 | public String where() { 164 | if (mWhereBuilder.length() == 0) { 165 | return null; 166 | } 167 | return mWhereBuilder.toString(); 168 | } 169 | 170 | @Nullable 171 | public String[] whereArgs() { 172 | if (mBindValues.isEmpty()) { 173 | return null; 174 | } 175 | String[] args = new String[mBindValues.size()]; 176 | for (int i = 0; i < mBindValues.size(); i++) { 177 | args[i] = String.valueOf(mBindValues.get(i)); 178 | } 179 | return args; 180 | } 181 | 182 | @Nullable 183 | public String limit() { 184 | String result = ""; 185 | if (mLimit != null && !mLimit.isEmpty()) { 186 | result += " LIMIT " + mLimit; 187 | } 188 | if (mOffset != null && !mOffset.isEmpty()) { 189 | result += " OFFSET " + mOffset; 190 | } 191 | if (result.isEmpty()) { 192 | return null; 193 | } 194 | return result; 195 | } 196 | 197 | @NonNull 198 | private Where where(@NonNull String column, @NonNull String operand, @NonNull Object... values) { 199 | mWhereBuilder.append(column).append(operand); 200 | Collections.addAll(mBindValues, values); 201 | return this; 202 | } 203 | 204 | } -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/rx/RxSQLite.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.rx; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.support.annotation.NonNull; 6 | 7 | import java.util.List; 8 | import java.util.concurrent.Callable; 9 | 10 | import io.reactivex.Observable; 11 | import io.reactivex.ObservableSource; 12 | import io.reactivex.disposables.Disposable; 13 | import io.reactivex.functions.Function; 14 | import ru.arturvasilov.sqlite.core.SQLite; 15 | import ru.arturvasilov.sqlite.core.Table; 16 | import ru.arturvasilov.sqlite.core.Where; 17 | 18 | /** 19 | * For the documentation please take a look at {@link SQLite}, 20 | * Most of the methods in this class simply wraps {@link SQLite} methods in Observables. 21 | *

22 | * Note: this class doesn't handled doing operations in the background - it's up to the user 23 | * (since in most cases you won't simply subscribe for data, but transform it, so you'll have to write 24 | * subscribeOn and observeOn one more time. 25 | * 26 | * @author Artur Vasilov 27 | */ 28 | public class RxSQLite { 29 | 30 | private static RxSQLite sSQLite; 31 | 32 | /** 33 | * Before calling this method be sure that you've successfully initialized SQLite instance 34 | * (by {@link SQLite#initialize(Context)}), since all of the methods in this class are simple wrappers. 35 | * 36 | * @return singleton instance of RxSQLite 37 | */ 38 | @NonNull 39 | public static RxSQLite get() { 40 | SQLite.get(); 41 | 42 | RxSQLite sqLite = sSQLite; 43 | if (sqLite == null) { 44 | synchronized (SQLite.class) { 45 | sqLite = sSQLite; 46 | if (sqLite == null) { 47 | sqLite = sSQLite = new RxSQLite(); 48 | } 49 | } 50 | } 51 | return sqLite; 52 | } 53 | 54 | /** 55 | * {@link SQLite#query(Table)} 56 | */ 57 | @NonNull 58 | public Observable> query(@NonNull final Table table) { 59 | return query(table, Where.create()); 60 | } 61 | 62 | /** 63 | * {@link SQLite#query(Table, Where)} 64 | */ 65 | @NonNull 66 | public Observable> query(@NonNull final Table table, @NonNull final Where where) { 67 | return Observable.fromCallable(new Callable>() { 68 | @Override 69 | public List call() throws Exception { 70 | return SQLite.get().query(table, where); 71 | } 72 | }); 73 | } 74 | 75 | /** 76 | * {@link SQLite#querySingle(Table)} 77 | */ 78 | @NonNull 79 | public Observable querySingle(@NonNull final Table table) { 80 | return querySingle(table, Where.create()); 81 | } 82 | 83 | /** 84 | * {@link SQLite#querySingle(Table, Where)} 85 | *

86 | * Observable is guarantee to contain no more than one element. 87 | * If you want to get observable of elements, simply call {@link RxSQLite#query(Table, Where)} 88 | * all apply {@link Observable#flatMap(Function)} with {@link Observable#fromIterable(Iterable)} to it. 89 | */ 90 | @NonNull 91 | public Observable querySingle(@NonNull final Table table, @NonNull final Where where) { 92 | return Observable.fromCallable(new Callable() { 93 | @Override 94 | public T call() throws Exception { 95 | return SQLite.get().querySingle(table, where); 96 | } 97 | }).flatMap(new Function>() { 98 | @Override 99 | public ObservableSource apply(T t) throws Exception { 100 | return t == null ? Observable.empty() : Observable.just(t); 101 | } 102 | }).take(1); 103 | } 104 | 105 | /** 106 | * {@link SQLite#insert(Table, Object)} 107 | */ 108 | @NonNull 109 | public Observable insert(@NonNull final Table table, @NonNull final T object) { 110 | return Observable.fromCallable(new Callable() { 111 | @Override 112 | public Uri call() throws Exception { 113 | return SQLite.get().insert(table, object); 114 | } 115 | }); 116 | } 117 | 118 | /** 119 | * {@link SQLite#insert(Table, List)} 120 | */ 121 | @NonNull 122 | public Observable insert(@NonNull final Table table, @NonNull final List objects) { 123 | return Observable.fromCallable(new Callable() { 124 | @Override 125 | public Integer call() throws Exception { 126 | return SQLite.get().insert(table, objects); 127 | } 128 | }); 129 | } 130 | 131 | /** 132 | * {@link SQLite#delete(Table)} 133 | */ 134 | @NonNull 135 | public Observable delete(@NonNull final Table table) { 136 | return delete(table, Where.create()); 137 | } 138 | 139 | /** 140 | * {@link SQLite#delete(Table, Where)} 141 | */ 142 | @NonNull 143 | public Observable delete(@NonNull final Table table, @NonNull final Where where) { 144 | return Observable.fromCallable(new Callable() { 145 | @Override 146 | public Integer call() throws Exception { 147 | return SQLite.get().delete(table, where); 148 | } 149 | }); 150 | } 151 | 152 | /** 153 | * {@link SQLite#update(Table, Where, Object)} 154 | */ 155 | @NonNull 156 | public Observable update(@NonNull final Table table, @NonNull final Where where, 157 | @NonNull final T newObject) { 158 | return Observable.fromCallable(new Callable() { 159 | @Override 160 | public Integer call() throws Exception { 161 | return SQLite.get().update(table, where, newObject); 162 | } 163 | }); 164 | } 165 | 166 | /** 167 | * Returns observable that emits new items when passed table is changed. 168 | * For more information please take a look at {@link TableObservable} 169 | *

170 | * This observable never completes and there is no methods in RxSQLite to dispose, 171 | * so you have to control disposables manually like this: 172 | *

173 | *

174 |      * {code
175 |      * private Disposable mPersonsDisposable;
176 |      *
177 |      * //...
178 |      *
179 |      * @Override
180 |      * protected void onResume() {
181 |      *  super.onResume();
182 |      *  mPersonsDisposable = RxSQLite.get().observeChanges(PersonTable.TABLE)
183 |      *      .subscribe(value -> {
184 |      *          // table changed
185 |      *      });
186 |      *  }
187 |      *
188 |      * @Override
189 |      * protected void onPause() {
190 |      *  super.onPause();
191 |      *  mPersonsDisposable.dispose();
192 |      * }
193 |      * }
194 |      * 
195 | * {@link Disposable#dispose()} will also detach ContentProvider observer 196 | */ 197 | @NonNull 198 | public TableObservable observeChanges(@NonNull final Table table) { 199 | return new TableObservable<>(table); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/rx/TableObservable.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.rx; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Observable; 8 | import io.reactivex.ObservableSource; 9 | import io.reactivex.Observer; 10 | import io.reactivex.android.MainThreadDisposable; 11 | import io.reactivex.functions.Function; 12 | import io.reactivex.schedulers.Schedulers; 13 | import ru.arturvasilov.sqlite.core.BasicTableObserver; 14 | import ru.arturvasilov.sqlite.core.SQLite; 15 | import ru.arturvasilov.sqlite.core.Table; 16 | import ru.arturvasilov.sqlite.core.Where; 17 | 18 | /** 19 | * Class which allows you to observe changes in {@link Table} in reactive way 20 | *

21 | * Simple call of {@link RxSQLite#observeChanges(Table)} will return you an instance of this observable, 22 | * and you can any operations with this observable. 23 | *

24 | * For this Observable {@link Observer#onNext(Object)} called each time when table changes 25 | * and {@link Observer#onComplete()} is never called. 26 | *

27 | * This observable add {@link TableObservable#withQuery()} method to allow you query all rows from the observed tabled. 28 | * 29 | * @author Artur Vasilov 30 | */ 31 | public class TableObservable extends Observable { 32 | 33 | private final Table mTable; 34 | 35 | TableObservable(@NonNull Table table) { 36 | mTable = table; 37 | } 38 | 39 | @Override 40 | protected void subscribeActual(Observer observer) { 41 | TableListener tableListener = new TableListener(observer); 42 | observer.onSubscribe(tableListener); 43 | SQLite.get().registerObserver(mTable, tableListener); 44 | } 45 | 46 | /** 47 | * This method transforms notifications observable to the observable with list of all objects in the table. 48 | *

49 | * It also works in the background. 50 | * 51 | * @return observable with all elements from the table 52 | */ 53 | @NonNull 54 | public Observable> withQuery() { 55 | return withQuery(Where.create()); 56 | } 57 | 58 | /** 59 | * This method transforms notifications observable to the observable 60 | * with list of all objects in the table which satisfies where parameter 61 | *

62 | * It also works in the background. 63 | * 64 | * @param where - arguments to query table 65 | * @return observable with all elements from the table 66 | */ 67 | @NonNull 68 | public Observable> withQuery(@NonNull final Where where) { 69 | return flatMap(new Function>>() { 70 | @Override 71 | public ObservableSource> apply(Boolean value) throws Exception { 72 | return RxSQLite.get().query(mTable, where); 73 | } 74 | }).subscribeOn(Schedulers.io()); 75 | } 76 | 77 | private class TableListener extends MainThreadDisposable implements BasicTableObserver { 78 | 79 | private final Observer mObserver; 80 | 81 | TableListener(@NonNull Observer observer) { 82 | mObserver = observer; 83 | } 84 | 85 | @Override 86 | public void onTableChanged() { 87 | mObserver.onNext(true); 88 | } 89 | 90 | @Override 91 | protected void onDispose() { 92 | SQLite.get().unregisterObserver(this); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/utils/SQLiteUtils.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.utils; 2 | 3 | import android.database.Cursor; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import ru.arturvasilov.sqlite.core.SQLite; 8 | import ru.arturvasilov.sqlite.core.Table; 9 | 10 | /** 11 | * Utility class for safe methods to work with database. 12 | * 13 | * @author Artur Vasilov 14 | */ 15 | public class SQLiteUtils { 16 | 17 | private SQLiteUtils() { 18 | } 19 | 20 | /** 21 | * Fast method to determine if any rows in database exists in table 22 | * 23 | * @param table to test if it's empty 24 | * @return whatever table is empty or not 25 | */ 26 | public static boolean isTableEmpty(@NonNull Table table) { 27 | Cursor cursor = SQLite.get().getContentResolver().query( 28 | table.getUri(), 29 | new String[] {"count(*) AS count"}, 30 | null, 31 | null, 32 | null); 33 | try { 34 | return cursor == null || cursor.getCount() == 0 35 | || !cursor.moveToFirst() || cursor.getInt(0) == 0; 36 | } 37 | finally { 38 | safeCloseCursor(cursor); 39 | } 40 | } 41 | 42 | /** 43 | * Method to test if any rows in cursor exists. It's safe, you can pass null cursor or closed, e.g. 44 | * 45 | * @param cursor - cursor you want to check if it's empty 46 | * @return - true if cursor is null, closed or empty or false in other cases 47 | */ 48 | public static boolean isEmptyCursor(@Nullable Cursor cursor) { 49 | if (cursor == null) { 50 | return true; 51 | } 52 | 53 | //noinspection SimplifiableIfStatement 54 | if (cursor.isClosed()) { 55 | return true; 56 | } 57 | 58 | return !cursor.moveToFirst(); 59 | } 60 | 61 | /** 62 | * Closing cursor was always the hell (since you have to check if it's null or closed and so on, 63 | * but this method handles all the cases and safely closes the cursor. 64 | * 65 | * @param cursor - cursor you want to close 66 | */ 67 | public static void safeCloseCursor(@Nullable Cursor cursor) { 68 | if (cursor == null || cursor.isClosed()) { 69 | return; 70 | } 71 | 72 | try { 73 | cursor.close(); 74 | } catch (Exception ignored) { 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/ru/arturvasilov/sqlite/utils/TableBuilder.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.VisibleForTesting; 5 | 6 | import org.sqlite.database.sqlite.SQLiteDatabase; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import ru.arturvasilov.sqlite.core.Table; 12 | 13 | /** 14 | * @author Artur Vasilov 15 | */ 16 | public final class TableBuilder { 17 | 18 | private String mTableName; 19 | private final List mPrimaryKeys; 20 | private final List mIntegerColumns; 21 | private final List mRealColumns; 22 | private final List mTextColumns; 23 | 24 | private TableBuilder() { 25 | mPrimaryKeys = new ArrayList<>(); 26 | mIntegerColumns = new ArrayList<>(); 27 | mRealColumns = new ArrayList<>(); 28 | mTextColumns = new ArrayList<>(); 29 | } 30 | 31 | @NonNull 32 | public static TableBuilder create(@NonNull Table table) { 33 | TableBuilder builder = new TableBuilder(); 34 | builder.mTableName = table.getTableName(); 35 | return builder; 36 | } 37 | 38 | @NonNull 39 | public TableBuilder primaryKey(@NonNull String... keys) { 40 | mPrimaryKeys.clear(); 41 | for (String key : keys) { 42 | if (!mPrimaryKeys.contains(key)) { 43 | mPrimaryKeys.add(key); 44 | } 45 | } 46 | return this; 47 | } 48 | 49 | @NonNull 50 | public TableBuilder intColumn(@NonNull String columnName) { 51 | if (!mIntegerColumns.contains(columnName)) { 52 | mIntegerColumns.add(columnName); 53 | } 54 | return this; 55 | } 56 | 57 | @NonNull 58 | public TableBuilder realColumn(@NonNull String columnName) { 59 | if (!mRealColumns.contains(columnName)) { 60 | mRealColumns.add(columnName); 61 | } 62 | return this; 63 | } 64 | 65 | @NonNull 66 | public TableBuilder textColumn(@NonNull String columnName) { 67 | if (!mTextColumns.contains(columnName)) { 68 | mTextColumns.add(columnName); 69 | } 70 | return this; 71 | } 72 | 73 | public void execute(@NonNull SQLiteDatabase database) { 74 | database.execSQL(buildSQL()); 75 | } 76 | 77 | @VisibleForTesting 78 | String buildSQL() { 79 | if (mIntegerColumns.isEmpty() && mRealColumns.isEmpty() && mTextColumns.isEmpty()) { 80 | throw new IllegalStateException("Cannot create table with no columns"); 81 | } 82 | 83 | StringBuilder builder = new StringBuilder(); 84 | builder.append("CREATE TABLE IF NOT EXISTS ") 85 | .append(mTableName) 86 | .append("("); 87 | 88 | if (!mIntegerColumns.isEmpty()) { 89 | String column = mIntegerColumns.remove(0); 90 | builder.append(column) 91 | .append(" INTEGER"); 92 | } else if (!mRealColumns.isEmpty()) { 93 | String column = mRealColumns.remove(0); 94 | builder.append(column) 95 | .append(" REAL"); 96 | } else { 97 | String column = mTextColumns.remove(0); 98 | builder.append(column) 99 | .append(" TEXT"); 100 | } 101 | 102 | for (String column : mIntegerColumns) { 103 | builder.append(", ") 104 | .append(column) 105 | .append(" INTEGER"); 106 | } 107 | 108 | for (String column : mRealColumns) { 109 | builder.append(", ") 110 | .append(column) 111 | .append(" REAL"); 112 | } 113 | 114 | for (String column : mTextColumns) { 115 | builder.append(", ") 116 | .append(column) 117 | .append(" TEXT"); 118 | } 119 | 120 | if (!mPrimaryKeys.isEmpty()) { 121 | builder.append(", PRIMARY KEY (") 122 | .append(mPrimaryKeys.get(0)); 123 | } 124 | for (int i = 1; i < mPrimaryKeys.size(); i++) { 125 | builder.append(", ").append(mPrimaryKeys.get(i)); 126 | } 127 | if (!mPrimaryKeys.isEmpty()) { 128 | builder.append(")"); 129 | } 130 | builder.append(");"); 131 | 132 | return builder.toString(); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /app/src/test/java/ru/arturvasilov/sqlite/core/WhereTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.core; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import junit.framework.Assert; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.junit.runners.JUnit4; 11 | 12 | import static junit.framework.Assert.assertNotNull; 13 | import static org.junit.Assert.assertEquals; 14 | import static org.junit.Assert.assertNull; 15 | 16 | /** 17 | * @author Artur Vasilov 18 | */ 19 | @RunWith(JUnit4.class) 20 | public class WhereTest { 21 | 22 | @Test 23 | public void testNoParameters() throws Exception { 24 | checkWhere(null, null, null, Where.create()); 25 | } 26 | 27 | @Test 28 | public void testEqualTo() throws Exception { 29 | Where where = Where.create().equalTo("id", 5); 30 | checkWhere("id = ?", new String[]{"5"}, null, where); 31 | } 32 | 33 | @Test 34 | public void testNotEqualTo() throws Exception { 35 | Where where = Where.create().notEqualTo("text", "abcd"); 36 | checkWhere("text <> ?", new String[]{"abcd"}, null, where); 37 | } 38 | 39 | @Test 40 | public void testLessThan() throws Exception { 41 | Where where = Where.create().lessThan("age", 18); 42 | checkWhere("age < ?", new String[]{"18"}, null, where); 43 | } 44 | 45 | @Test 46 | public void testLessThanOrEqualTo() throws Exception { 47 | Where where = Where.create().lessThanOrEqualTo("price", 207.18); 48 | checkWhere("price <= ?", new String[]{"207.18"}, null, where); 49 | } 50 | 51 | @Test 52 | public void testGreaterThan() throws Exception { 53 | Where where = Where.create().greaterThan("year", 2008); 54 | checkWhere("year > ?", new String[]{"2008"}, null, where); 55 | } 56 | 57 | @Test 58 | public void testGreaterThanOrEqualTo() throws Exception { 59 | Where where = Where.create().greaterThanOrEqualTo("age", 18); 60 | checkWhere("age >= ?", new String[]{"18"}, null, where); 61 | } 62 | 63 | @Test 64 | public void testLike() throws Exception { 65 | Where where = Where.create().like("title", "Politics"); 66 | checkWhere("title LIKE ?", new String[]{"Politics"}, null, where); 67 | } 68 | 69 | @Test 70 | public void testBetween() throws Exception { 71 | Where where = Where.create().between("price", 17.5, 19.8); 72 | checkWhere("price BETWEEN ? AND ?", new String[]{"17.5", "19.8"}, null, where); 73 | } 74 | 75 | @Test 76 | public void testIsNull() throws Exception { 77 | Where where = Where.create().isNull("body"); 78 | checkWhere("body IS NULL", null, null, where); 79 | } 80 | 81 | @Test 82 | public void testNotNull() throws Exception { 83 | Where where = Where.create().notNull("body"); 84 | checkWhere("body NOT NULL", null, null, where); 85 | } 86 | 87 | @Test 88 | public void testIn() throws Exception { 89 | Where where = Where.create().in("id", 5, 6, 7, 9); 90 | checkWhere("id IN(?, ?, ?, ?)", new String[]{"5", "6", "7", "9"}, null, where); 91 | } 92 | 93 | @Test 94 | public void testAnd() throws Exception { 95 | Where where = Where.create().greaterThanOrEqualTo("age", 18).and().lessThan("age", 45); 96 | checkWhere("age >= ? AND age < ?", new String[]{"18", "45"}, null, where); 97 | } 98 | 99 | @Test 100 | public void testOr() throws Exception { 101 | Where where = Where.create().equalTo("city", "London").or().like("country", "R"); 102 | checkWhere("city = ? OR country LIKE ?", new String[]{"London", "R"}, null, where); 103 | } 104 | 105 | @Test 106 | public void testLimit() throws Exception { 107 | Where where = Where.create().limit(3); 108 | checkWhere(null, null, " LIMIT 3", where); 109 | } 110 | 111 | @Test 112 | public void testOffset() throws Exception { 113 | Where where = Where.create().offset(7); 114 | checkWhere(null, null, " OFFSET 7", where); 115 | } 116 | 117 | @Test 118 | public void testLimitAndOffset() throws Exception { 119 | Where where = Where.create().limit(1).offset(18); 120 | checkWhere(null, null, " LIMIT 1 OFFSET 18", where); 121 | } 122 | 123 | @Test 124 | public void testWhere() throws Exception { 125 | Where where = Where.create().where("id > ? AND age <= ?", 1000, 60); 126 | checkWhere("id > ? AND age <= ?", new String[]{"1000", "60"}, null, where); 127 | } 128 | 129 | @Test 130 | public void testGroups() throws Exception { 131 | Where where = Where.create() 132 | .beginGroup() 133 | .greaterThanOrEqualTo("age", 18) 134 | .or() 135 | .lessThan("age", 50) 136 | .endGroup() 137 | .and() 138 | .like("name", "Sm"); 139 | checkWhere("(age >= ? OR age < ?) AND name LIKE ?", new String[]{"18", "50", "Sm"}, null, where); 140 | } 141 | 142 | @Test 143 | public void testComplexQuery() throws Exception { 144 | Where where = Where.create() 145 | .in("id", 18, 20, 24) 146 | .and() 147 | .like("text", "hello") 148 | .limit(1); 149 | 150 | checkWhere("id IN(?, ?, ?) AND text LIKE ?", new String[]{"18", "20", "24", "hello"}, " LIMIT 1", where); 151 | } 152 | 153 | private void checkWhere(@Nullable String where, @Nullable String[] args, 154 | @Nullable String limit, @NonNull Where testWhere) { 155 | assertEquals(where, testWhere.where()); 156 | if (args == null) { 157 | assertNull(testWhere.whereArgs()); 158 | } else { 159 | String[] whereArgs = testWhere.whereArgs(); 160 | assertNotNull(whereArgs); 161 | assertEquals(args.length, whereArgs.length); 162 | for (int i = 0; i < args.length; i++) { 163 | assertEquals(args[i], whereArgs[i]); 164 | } 165 | } 166 | Assert.assertEquals(limit, testWhere.limit()); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/test/java/ru/arturvasilov/sqlite/testutils/JUnitTestObject.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.testutils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | /** 6 | * @author Artur Vasilov 7 | */ 8 | public class JUnitTestObject { 9 | 10 | private final int mId; 11 | private final double mRating; 12 | private final String mText; 13 | 14 | public JUnitTestObject(int id, double rating, @NonNull String text) { 15 | mId = id; 16 | mRating = rating; 17 | mText = text; 18 | } 19 | 20 | public int getId() { 21 | return mId; 22 | } 23 | 24 | public double getRating() { 25 | return mRating; 26 | } 27 | 28 | @NonNull 29 | public String getText() { 30 | return mText; 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) return true; 36 | if (o == null || getClass() != o.getClass()) return false; 37 | 38 | JUnitTestObject that = (JUnitTestObject) o; 39 | 40 | return getId() == that.getId() && Math.abs(getRating() - that.getRating()) < 0.000001 41 | && getText().equals(that.getText()); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | int result = getId(); 47 | result = 31 * result + getText().hashCode(); 48 | return result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/test/java/ru/arturvasilov/sqlite/testutils/JUnitTestTable.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.testutils; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | import android.support.annotation.NonNull; 6 | 7 | import org.sqlite.database.sqlite.SQLiteDatabase; 8 | 9 | import ru.arturvasilov.sqlite.core.BaseTable; 10 | import ru.arturvasilov.sqlite.core.Table; 11 | import ru.arturvasilov.sqlite.utils.TableBuilder; 12 | 13 | /** 14 | * @author Artur Vasilov 15 | */ 16 | public class JUnitTestTable extends BaseTable { 17 | 18 | public static final Table TABLE = new JUnitTestTable(); 19 | 20 | public static final String ID = "id"; 21 | public static final String RATING = "rating"; 22 | public static final String TEXT = "text"; 23 | 24 | @Override 25 | public void onCreate(@NonNull SQLiteDatabase database) { 26 | TableBuilder.create(this) 27 | .intColumn(ID) 28 | .realColumn(RATING) 29 | .textColumn(TEXT) 30 | .primaryKey(ID) 31 | .execute(database); 32 | } 33 | 34 | @NonNull 35 | @Override 36 | public ContentValues toValues(@NonNull JUnitTestObject testObject) { 37 | ContentValues values = new ContentValues(); 38 | values.put(ID, testObject.getId()); 39 | values.put(RATING, testObject.getRating()); 40 | values.put(TEXT, testObject.getText()); 41 | return values; 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public JUnitTestObject fromCursor(@NonNull Cursor cursor) { 47 | int id = cursor.getInt(cursor.getColumnIndex(ID)); 48 | double rating = cursor.getDouble(cursor.getColumnIndex(RATING)); 49 | String text = cursor.getString(cursor.getColumnIndex(TEXT)); 50 | return new JUnitTestObject(id, rating, text); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/test/java/ru/arturvasilov/sqlite/utils/SQLiteUtilsTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.utils; 2 | 3 | import android.database.Cursor; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.JUnit4; 8 | 9 | import static junit.framework.Assert.assertFalse; 10 | import static junit.framework.Assert.assertTrue; 11 | import static org.mockito.Mockito.doNothing; 12 | import static org.mockito.Mockito.doThrow; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.never; 15 | import static org.mockito.Mockito.verify; 16 | import static org.mockito.Mockito.when; 17 | 18 | /** 19 | * @author Artur Vasilov 20 | */ 21 | @RunWith(JUnit4.class) 22 | public class SQLiteUtilsTest { 23 | 24 | @Test 25 | public void testCursorEmptyNull() throws Exception { 26 | boolean isEmpty = SQLiteUtils.isEmptyCursor(null); 27 | //noinspection ConstantConditions 28 | assertTrue(isEmpty); 29 | } 30 | 31 | @Test 32 | public void testCursorEmptyClosed() throws Exception { 33 | Cursor cursor = mock(Cursor.class); 34 | when(cursor.isClosed()).thenReturn(true); 35 | 36 | boolean isEmpty = SQLiteUtils.isEmptyCursor(cursor); 37 | //noinspection ConstantConditions 38 | assertTrue(isEmpty); 39 | } 40 | 41 | @Test 42 | public void testCursorEmpty() throws Exception { 43 | Cursor cursor = mock(Cursor.class); 44 | when(cursor.isClosed()).thenReturn(true); 45 | when(cursor.moveToFirst()).thenReturn(false); 46 | 47 | boolean isEmpty = SQLiteUtils.isEmptyCursor(cursor); 48 | //noinspection ConstantConditions 49 | assertTrue(isEmpty); 50 | } 51 | 52 | @Test 53 | public void testCursorNotEmpty() throws Exception { 54 | Cursor cursor = mock(Cursor.class); 55 | when(cursor.isClosed()).thenReturn(false); 56 | when(cursor.moveToFirst()).thenReturn(true); 57 | 58 | boolean isEmpty = SQLiteUtils.isEmptyCursor(cursor); 59 | //noinspection ConstantConditions 60 | assertFalse(isEmpty); 61 | } 62 | 63 | @Test 64 | public void testCloseNullCursor() throws Exception { 65 | SQLiteUtils.safeCloseCursor(null); 66 | } 67 | 68 | @Test 69 | public void testClosedCursor() throws Exception { 70 | Cursor cursor = mock(Cursor.class); 71 | when(cursor.isClosed()).thenReturn(true); 72 | SQLiteUtils.safeCloseCursor(cursor); 73 | 74 | verify(cursor, never()).close(); 75 | } 76 | 77 | @Test 78 | public void testExceptionCursor() throws Exception { 79 | Cursor cursor = mock(Cursor.class); 80 | when(cursor.isClosed()).thenReturn(false); 81 | doThrow(new RuntimeException()).when(cursor).close(); 82 | SQLiteUtils.safeCloseCursor(cursor); 83 | } 84 | 85 | @Test 86 | public void testCloseCursor() throws Exception { 87 | Cursor cursor = mock(Cursor.class); 88 | when(cursor.isClosed()).thenReturn(false); 89 | doNothing().when(cursor).close(); 90 | SQLiteUtils.safeCloseCursor(cursor); 91 | 92 | verify(cursor).close(); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /app/src/test/java/ru/arturvasilov/sqlite/utils/TableBuilderTest.java: -------------------------------------------------------------------------------- 1 | package ru.arturvasilov.sqlite.utils; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.JUnit4; 6 | 7 | import ru.arturvasilov.sqlite.testutils.JUnitTestTable; 8 | 9 | import static junit.framework.TestCase.assertEquals; 10 | 11 | @RunWith(JUnit4.class) 12 | public class TableBuilderTest { 13 | 14 | @Test(expected = IllegalStateException.class) 15 | public void testEmptyColumnsList() throws Exception { 16 | TableBuilder.create(JUnitTestTable.TABLE).buildSQL(); 17 | } 18 | 19 | @Test 20 | public void testSingleIntegerColumn() throws Exception { 21 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test INTEGER);"; 22 | 23 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 24 | .intColumn("test") 25 | .buildSQL()); 26 | } 27 | 28 | @Test 29 | public void testSingleRealColumn() throws Exception { 30 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test REAL);"; 31 | 32 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 33 | .realColumn("test") 34 | .buildSQL()); 35 | } 36 | 37 | @Test 38 | public void testSingleStringColumn() throws Exception { 39 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test TEXT);"; 40 | 41 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 42 | .textColumn("test") 43 | .buildSQL()); 44 | } 45 | 46 | @Test 47 | public void testPrimaryKeyWithIntColumn() throws Exception { 48 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test INTEGER, PRIMARY KEY (test));"; 49 | 50 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 51 | .intColumn("test") 52 | .primaryKey("test") 53 | .buildSQL()); 54 | } 55 | 56 | @Test 57 | public void testManyColumnsWithoutPrimaryKey() throws Exception { 58 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(int1 INTEGER, int2 INTEGER, " + 59 | "real1 REAL, string1 TEXT, string2 TEXT);"; 60 | 61 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 62 | .intColumn("int1") 63 | .textColumn("string1") 64 | .intColumn("int2") 65 | .realColumn("real1") 66 | .textColumn("string2") 67 | .buildSQL()); 68 | } 69 | 70 | @Test 71 | public void testManyColumnsWithPrimaryKey() throws Exception { 72 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(int1 INTEGER, int2 INTEGER," + 73 | " real1 REAL, string1 TEXT, string2 TEXT, PRIMARY KEY (int1));"; 74 | 75 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 76 | .intColumn("int1") 77 | .textColumn("string1") 78 | .intColumn("int2") 79 | .realColumn("real1") 80 | .textColumn("string2") 81 | .primaryKey("int1") 82 | .buildSQL()); 83 | } 84 | 85 | @Test 86 | public void testManyColumnsWithMultiplePrimaryKey() throws Exception { 87 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(int1 INTEGER, int2 INTEGER, " + 88 | "real1 REAL, real2 REAL, string1 TEXT, string2 TEXT, PRIMARY KEY (int1, real1, string2));"; 89 | 90 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 91 | .intColumn("int1") 92 | .realColumn("real1") 93 | .textColumn("string1") 94 | .intColumn("int2") 95 | .textColumn("string2") 96 | .realColumn("real2") 97 | .primaryKey("int1", "real1", "string2") 98 | .buildSQL()); 99 | } 100 | 101 | @Test 102 | public void testSameColumnsIgnored() throws Exception { 103 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(testInt INTEGER, testReal REAL, testText TEXT);"; 104 | 105 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 106 | .intColumn("testInt") 107 | .intColumn("testInt") 108 | .realColumn("testReal") 109 | .realColumn("testReal") 110 | .textColumn("testText") 111 | .textColumn("testText") 112 | .buildSQL()); 113 | } 114 | 115 | @Test 116 | public void testSamePrimaryKeysIgnored() throws Exception { 117 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test INTEGER, PRIMARY KEY (test));"; 118 | 119 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE) 120 | .intColumn("test") 121 | .primaryKey("test", "test") 122 | .buildSQL()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:2.3.0-beta3' 7 | classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' 8 | classpath 'org.jacoco:org.jacoco.core:0.7.8' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | jcenter() 15 | flatDir { dirs 'libs' } 16 | } 17 | } 18 | 19 | ext { 20 | PUBLISH_GROUP_ID = 'ru.arturvasilov' 21 | PUBLISH_ARTIFACT_ID = 'sqlite' 22 | PUBLISH_VERSION = '0.2.0' 23 | } 24 | 25 | allprojects { 26 | repositories { 27 | jcenter() 28 | } 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | 35 | task wrapper(type: Wrapper) { 36 | gradleVersion = '3.3' 37 | } 38 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturVasilov/SQLite/e8fb5318a4aeee3a8744a4240392524cca7cdde5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 31 15:25:15 MSK 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows 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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------