Repository: guolindev/LitePal Branch: master Commit: 8ad8322cc6f8 Files: 223 Total size: 1.1 MB Directory structure: gitextract__kl11lqh/ ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── core/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── org/ │ │ └── litepal/ │ │ ├── FluentQuery.java │ │ ├── LitePal.kt │ │ ├── LitePalApplication.java │ │ ├── LitePalBase.java │ │ ├── LitePalDB.java │ │ ├── Operator.java │ │ ├── annotation/ │ │ │ ├── Column.java │ │ │ └── Encrypt.java │ │ ├── crud/ │ │ │ ├── AssociationsAnalyzer.java │ │ │ ├── DataHandler.java │ │ │ ├── DeleteHandler.java │ │ │ ├── DynamicExecutor.java │ │ │ ├── LitePalSupport.java │ │ │ ├── Many2ManyAnalyzer.java │ │ │ ├── Many2OneAnalyzer.java │ │ │ ├── One2OneAnalyzer.java │ │ │ ├── QueryHandler.java │ │ │ ├── SaveHandler.java │ │ │ ├── UpdateHandler.java │ │ │ ├── async/ │ │ │ │ ├── AsyncExecutor.java │ │ │ │ ├── AverageExecutor.java │ │ │ │ ├── CountExecutor.java │ │ │ │ ├── FindExecutor.java │ │ │ │ ├── FindMultiExecutor.java │ │ │ │ ├── SaveExecutor.java │ │ │ │ └── UpdateOrDeleteExecutor.java │ │ │ ├── callback/ │ │ │ │ ├── AverageCallback.java │ │ │ │ ├── CountCallback.java │ │ │ │ ├── FindCallback.java │ │ │ │ ├── FindMultiCallback.java │ │ │ │ ├── SaveCallback.java │ │ │ │ └── UpdateOrDeleteCallback.java │ │ │ └── model/ │ │ │ └── AssociationsInfo.java │ │ ├── exceptions/ │ │ │ ├── DataSupportException.java │ │ │ ├── DatabaseGenerateException.java │ │ │ ├── GlobalException.java │ │ │ ├── InvalidAttributesException.java │ │ │ ├── LitePalSupportException.java │ │ │ └── ParseConfigurationFileException.java │ │ ├── extension/ │ │ │ ├── FluentQuery.kt │ │ │ └── LitePal.kt │ │ ├── model/ │ │ │ └── Table_Schema.java │ │ ├── parser/ │ │ │ ├── LitePalAttr.java │ │ │ ├── LitePalConfig.java │ │ │ ├── LitePalContentHandler.java │ │ │ └── LitePalParser.java │ │ ├── tablemanager/ │ │ │ ├── AssociationCreator.java │ │ │ ├── AssociationUpdater.java │ │ │ ├── Connector.java │ │ │ ├── Creator.java │ │ │ ├── Dropper.java │ │ │ ├── Generator.java │ │ │ ├── LitePalOpenHelper.java │ │ │ ├── Upgrader.java │ │ │ ├── callback/ │ │ │ │ └── DatabaseListener.java │ │ │ ├── model/ │ │ │ │ ├── AssociationsModel.java │ │ │ │ ├── ColumnModel.java │ │ │ │ ├── GenericModel.java │ │ │ │ └── TableModel.java │ │ │ └── typechange/ │ │ │ ├── BlobOrm.java │ │ │ ├── BooleanOrm.java │ │ │ ├── DateOrm.java │ │ │ ├── DecimalOrm.java │ │ │ ├── NumericOrm.java │ │ │ ├── OrmChange.java │ │ │ └── TextOrm.java │ │ └── util/ │ │ ├── BaseUtility.java │ │ ├── Const.java │ │ ├── DBUtility.java │ │ ├── LitePalLog.java │ │ ├── SharedUtil.java │ │ └── cipher/ │ │ ├── AESCrypt.java │ │ └── CipherUtil.java │ └── res/ │ └── values/ │ └── strings.xml ├── downloads/ │ ├── litepal-1.1.0-src.jar │ ├── litepal-1.1.0.jar │ ├── litepal-1.1.1-src.jar │ ├── litepal-1.1.1.jar │ ├── litepal-1.2.0-src.jar │ ├── litepal-1.2.0.jar │ ├── litepal-1.2.1-src.jar │ ├── litepal-1.2.1.jar │ ├── litepal-1.3.0-src.jar │ ├── litepal-1.3.0.jar │ ├── litepal-1.3.1-src.jar │ ├── litepal-1.3.1.jar │ ├── litepal-1.3.2-src.jar │ ├── litepal-1.3.2.jar │ ├── litepal-1.4.0-src.jar │ ├── litepal-1.4.0.jar │ ├── litepal-1.4.1-src.jar │ ├── litepal-1.4.1.jar │ ├── litepal-1.5.1-src.jar │ ├── litepal-1.5.1.jar │ ├── litepal-1.6.0-src.jar │ ├── litepal-1.6.0.jar │ ├── litepal-1.6.1-src.jar │ ├── litepal-1.6.1.jar │ ├── litepal-2.0.0-src.jar │ └── litepal-2.0.0.jar ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── java/ │ ├── .gitignore │ ├── bintray.gradle │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── org/ │ │ └── litepal/ │ │ └── LitePal.java │ └── res/ │ └── values/ │ └── strings.xml ├── kotlin/ │ ├── .gitignore │ ├── bintray.gradle │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── org/ │ │ └── litepal/ │ │ ├── LitePal.kt │ │ └── extension/ │ │ ├── FluentQuery.kt │ │ └── LitePal.kt │ └── res/ │ └── values/ │ └── strings.xml ├── sample/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── litepaltest/ │ │ ├── model/ │ │ │ ├── Book.java │ │ │ ├── Cellphone.java │ │ │ ├── Classroom.java │ │ │ ├── Computer.java │ │ │ ├── Headset.java │ │ │ ├── IdCard.java │ │ │ ├── Message.java │ │ │ ├── Product.java │ │ │ ├── Student.java │ │ │ ├── Teacher.java │ │ │ ├── WeChatMessage.java │ │ │ └── WeiboMessage.java │ │ └── test/ │ │ ├── LitePalTestCase.java │ │ ├── MultiDatabaseTest.java │ │ ├── annotation/ │ │ │ └── ColumnTest.java │ │ ├── crud/ │ │ │ ├── delete/ │ │ │ │ ├── DeleteKotlinTest.kt │ │ │ │ └── DeleteTest.java │ │ │ ├── query/ │ │ │ │ ├── QueryBasicKotlinTest.kt │ │ │ │ ├── QueryBasicTest.java │ │ │ │ ├── QueryBySQLTest.java │ │ │ │ ├── QueryClusterKotlinTest.kt │ │ │ │ ├── QueryClusterTest.java │ │ │ │ ├── QueryDateTest.java │ │ │ │ ├── QueryEagerKotlinTest.kt │ │ │ │ ├── QueryEagerTest.java │ │ │ │ ├── QueryMathKotlinTest.kt │ │ │ │ └── QueryMathTest.java │ │ │ ├── save/ │ │ │ │ ├── Many2ManySaveTest.java │ │ │ │ ├── Many2OneBiSaveTest.java │ │ │ │ ├── Many2OneUniSaveTest.java │ │ │ │ ├── One2OneBiSaveTest.java │ │ │ │ ├── One2OneUniSaveTest.java │ │ │ │ ├── SaveAllKotlinTest.kt │ │ │ │ ├── SaveAllTest.java │ │ │ │ └── SaveTest.java │ │ │ ├── transaction/ │ │ │ │ ├── TransactionKotlinTest.kt │ │ │ │ └── TransactionTest.java │ │ │ └── update/ │ │ │ ├── UpdateUsingSaveMethodTest.java │ │ │ ├── UpdateUsingUpdateMethodKotlinTest.kt │ │ │ └── UpdateUsingUpdateMethodTest.java │ │ └── util/ │ │ ├── BaseUtilityTest.java │ │ └── DBUtilityTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── litepal.xml │ ├── java/ │ │ └── org/ │ │ └── litepal/ │ │ └── litepalsample/ │ │ ├── MyApplication.java │ │ ├── activity/ │ │ │ ├── AggregateActivity.java │ │ │ ├── AverageSampleActivity.java │ │ │ ├── CRUDActivity.java │ │ │ ├── CountSampleActivity.java │ │ │ ├── DeleteSampleActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── ManageTablesActivity.java │ │ │ ├── MaxSampleActivity.java │ │ │ ├── MinSampleActivity.java │ │ │ ├── ModelListActivity.java │ │ │ ├── ModelStructureActivity.java │ │ │ ├── QuerySampleActivity.java │ │ │ ├── SaveSampleActivity.java │ │ │ ├── SumSampleActivity.java │ │ │ ├── TableListActivity.java │ │ │ ├── TableStructureActivity.java │ │ │ └── UpdateSampleActivity.java │ │ ├── adapter/ │ │ │ ├── DataArrayAdapter.java │ │ │ └── StringArrayAdapter.java │ │ ├── model/ │ │ │ ├── Album.java │ │ │ ├── Singer.java │ │ │ └── Song.java │ │ └── util/ │ │ └── Utility.java │ └── res/ │ ├── layout/ │ │ ├── aggregate_layout.xml │ │ ├── average_sample_layout.xml │ │ ├── count_sample_layout.xml │ │ ├── crud_layout.xml │ │ ├── delete_sample_layout.xml │ │ ├── main_layout.xml │ │ ├── manage_tables_layout.xml │ │ ├── max_sample_layout.xml │ │ ├── min_sample_layout.xml │ │ ├── model_list_layout.xml │ │ ├── model_structure_item.xml │ │ ├── model_structure_layout.xml │ │ ├── query_sample_layout.xml │ │ ├── save_sample_layout.xml │ │ ├── simple_list_item.xml │ │ ├── sum_sample_layout.xml │ │ ├── table_list_layout.xml │ │ ├── table_structure_item.xml │ │ ├── table_structure_layout.xml │ │ └── update_sample_layout.xml │ └── values/ │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .gradle gradle.properties /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build bin tmp gen ################# ## #IntelliJ IDEA ################# .idea *.iml *.ipr *.iws out ################# ## Eclipse ################# *.pydevproject .metadata *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath *.classpath *.project README.md~ /releaseToBintray.txt ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # LitePal for Android ![Logo](https://github.com/LitePalFramework/LitePal/blob/master/sample/src/main/logo/mini_logo.png) [中文文档](https://blog.csdn.net/sinyu890807/category_9262963.html) LitePal is an open source Android library that allows developers to use SQLite database extremely easy. You can finish most of the database operations without writing even a SQL statement, including create or upgrade tables, crud operations, aggregate functions, etc. The setup of LitePal is quite simple as well, you can integrate it into your project in less than 5 minutes. Experience the magic right now and have fun! ## Features * Using object-relational mapping (ORM) pattern. * Almost zero-configuration(only one configuration file with few properties). * Maintains all tables automatically(e.g. create, alter or drop tables). * Multi databases supported. * Encapsulated APIs for avoiding writing SQL statements. * Awesome fluent query API. * Alternative choice to use SQL still, but easier and better APIs than the originals. * More for you to explore. ## Quick Setup #### 1. Include library Edit your **build.gradle** file and add below dependency. ``` groovy dependencies { implementation 'org.litepal.guolindev:core:3.2.3' } ``` #### 2. Configure litepal.xml Create a file in the **assets** folder of your project and name it as **litepal.xml**. Then copy the following codes into it. ``` xml ``` This is the only configuration file, and the properties are simple. * **dbname** configure the database name of project. * **version** configure the version of database. Each time you want to upgrade database, plus the value here. * **list** configure the mapping classes. * **storage** configure where the database file should be stored. **internal** and **external** are the only valid options. #### 3. Configure LitePalApplication You don't want to pass the Context param all the time. To makes the APIs simple, just configure the LitePalApplication in **AndroidManifest.xml** as below: ``` xml ... ``` Of course you may have your own Application and has already configured here, like: ``` xml ... ``` That's OK. LitePal can still live with that. Just call **LitePal.initialize(context)** in your own Application: ```java public class MyOwnApplication extends Application { @Override public void onCreate() { super.onCreate(); LitePal.initialize(this); } ... } ``` Make sure to call this method as early as you can. In the **onCreate()** method of Application will be fine. And always remember to use the application context as parameter. Do not use any instance of activity or service as parameter, or memory leaks might happen. ## Get Started After setup, you can experience the powerful functions now. #### 1. Create tables Define the models first. For example you have two models, **Album** and **Song**. The models can be defined as below: ``` java public class Album extends LitePalSupport { @Column(unique = true, defaultValue = "unknown") private String name; @Column(index = true) private float price; private List songs = new ArrayList<>(); // generated getters and setters. ... } ``` ``` java public class Song extends LitePalSupport { @Column(nullable = false) private String name; private int duration; @Column(ignore = true) private String uselessField; private Album album; // generated getters and setters. ... } ``` Then add these models into the mapping list in **litepal.xml**: ``` xml ``` OK! The tables will be generated next time you operate database. For example, gets the **SQLiteDatabase** with following codes: ``` java SQLiteDatabase db = LitePal.getDatabase(); ``` Now the tables will be generated automatically with SQLs like this: ``` sql CREATE TABLE album ( id integer primary key autoincrement, name text unique default 'unknown', price real ); CREATE TABLE song ( id integer primary key autoincrement, name text not null, duration integer, album_id integer ); ``` #### 2. Upgrade tables Upgrade tables in LitePal is extremely easy. Just modify your models anyway you want: ```java public class Album extends LitePalSupport { @Column(unique = true, defaultValue = "unknown") private String name; @Column(ignore = true) private float price; private Date releaseDate; private List songs = new ArrayList<>(); // generated getters and setters. ... } ``` A **releaseDate** field was added and **price** field was annotated to ignore. Then increase the version number in **litepal.xml**: ```xml ``` The tables will be upgraded next time you operate database. A **releasedate** column will be added into **album** table and the original **price** column will be removed. All the data in **album** table except those removed columns will be retained. But there are some upgrading conditions that LitePal can't handle and all data in the upgrading table will be cleaned: * Add a field which annotated as `unique = true`. * Change a field's annotation into `unique = true`. * Change a field's annotation into `nullable = false`. Be careful of the above conditions which will cause losing data. #### 3. Save data The saving API is quite object oriented. Each model which inherits from **LitePalSupport** would have the **save()** method for free. Java: ``` java Album album = new Album(); album.setName("album"); album.setPrice(10.99f); album.setCover(getCoverImageBytes()); album.save(); Song song1 = new Song(); song1.setName("song1"); song1.setDuration(320); song1.setAlbum(album); song1.save(); Song song2 = new Song(); song2.setName("song2"); song2.setDuration(356); song2.setAlbum(album); song2.save(); ``` Kotlin: ```kotlin val album = Album() album.name = "album" album.price = 10.99f album.cover = getCoverImageBytes() album.save() val song1 = Song() song1.name = "song1" song1.duration = 320 song1.album = album song1.save() val song2 = Song() song2.name = "song2" song2.duration = 356 song2.album = album song2.save() ``` This will insert album, song1 and song2 into database with associations. #### 4. Update data The simplest way, use **save()** method to update a record found by **find()**. Java: ``` java Album albumToUpdate = LitePal.find(Album.class, 1); albumToUpdate.setPrice(20.99f); // raise the price albumToUpdate.save(); ``` Kotlin: ```kotlin val albumToUpdate = LitePal.find(1) albumToUpdate.price = 20.99f // raise the price albumToUpdate.save() ``` Each model which inherits from **LitePalSupport** would also have **update()** and **updateAll()** method. You can update a single record with a specified id. Java: ``` java Album albumToUpdate = new Album(); albumToUpdate.setPrice(20.99f); // raise the price albumToUpdate.update(id); ``` Kotlin: ```kotlin val albumToUpdate = Album() albumToUpdate.price = 20.99f // raise the price albumToUpdate.update(id) ``` Or you can update multiple records with a where condition. Java: ``` java Album albumToUpdate = new Album(); albumToUpdate.setPrice(20.99f); // raise the price albumToUpdate.updateAll("name = ?", "album"); ``` Kotlin: ```kotlin val albumToUpdate = Album() albumToUpdate.price = 20.99f // raise the price albumToUpdate.updateAll("name = ?", "album") ``` #### 5. Delete data You can delete a single record using the static **delete()** method in **LitePal**. Java: ``` java LitePal.delete(Song.class, id); ``` Kotlin: ```kotlin LitePal.delete(id) ``` Or delete multiple records using the static **deleteAll()** method in **LitePal**. Java: ``` java LitePal.deleteAll(Song.class, "duration > ?" , "350"); ``` Kotlin: ```kotlin LitePal.deleteAll("duration > ?" , "350") ``` #### 6. Query data Find a single record from song table with specified id. Java: ``` java Song song = LitePal.find(Song.class, id); ``` Kotlin: ```kotlin val song = LitePal.find(id) ``` Find all records from song table. Java: ``` java List allSongs = LitePal.findAll(Song.class); ``` Kotlin: ```kotlin val allSongs = LitePal.findAll() ``` Constructing complex query with fluent query. Java: ``` java List songs = LitePal.where("name like ? and duration < ?", "song%", "200").order("duration").find(Song.class); ``` Kotlin: ``` kotlin val songs = LitePal.where("name like ? and duration < ?", "song%", "200").order("duration").find() ``` #### 7. Multiple databases If your app needs multiple databases, LitePal support it completely. You can create as many databases as you want at runtime. For example: ```java LitePalDB litePalDB = new LitePalDB("demo2", 1); litePalDB.addClassName(Singer.class.getName()); litePalDB.addClassName(Album.class.getName()); litePalDB.addClassName(Song.class.getName()); LitePal.use(litePalDB); ``` This will create a **demo2** database with **singer**, **album** and **song** tables. If you just want to create a new database but with same configuration as **litepal.xml**, you can do it with: ```java LitePalDB litePalDB = LitePalDB.fromDefault("newdb"); LitePal.use(litePalDB); ``` You can always switch back to default database with: ```java LitePal.useDefault(); ``` And you can delete any database by specified database name: ```java LitePal.deleteDatabase("newdb"); ``` #### 8. Transaction LitePal support transaction for atomic db operations. All operations in the transaction will be committed or rolled back together. Java usage: ```java LitePal.beginTransaction(); boolean result1 = // db operation1 boolean result2 = // db operation2 boolean result3 = // db operation3 if (result1 && result2 && result3) { LitePal.setTransactionSuccessful(); } LitePal.endTransaction(); ``` Kotlin usage: ```kotlin LitePal.runInTransaction { val result1 = // db operation1 val result2 = // db operation2 val result3 = // db operation3 result1 && result2 && result3 } ``` ## ProGuard If you are using ProGuard you might need to add the following option: ```proguard -keep class org.litepal.** {*;} -keep class * extends org.litepal.crud.LitePalSupport {*;} ``` ## Bugs Report If you find any bug when using LitePal, please report **[here](https://github.com/LitePalFramework/LitePal/issues/new)**. Thanks for helping us making better. ## Change logs ### 3.2.3 * Support database index by adding @Column(index = true) on field. * Adding return value for **runInTransaction()** function for Kotlin. * Fix known bugs. ### 3.1.1 * Support transaction. * Add return value for **LitePal.saveAll()** method. * No longer support byte array field as column in table. * Deprecate all async methods. You should handle async operations by yourself. * Fix known bugs. ### 3.0.0 * Optimize generic usage for async operation APIs. * Add **LitePal.registerDatabaseListener()** method for listening create or upgrade database events. * Provider better CRUD API usage for using generic declaration instead of Class parameter for kotlin. * Fix known bugs. ### 2.0.0 * Offer new APIs for CRUD operations. Deprecate **DataSupport**, use **LitePal** and **LitePalSupport** instead. * Fully support kotlin programming. * Fix known bugs. ### 1.6.1 * Support AES and MD5 encryption with @Encrypt annotation on fields. * Support to store database file on any directory of external storage. * Fix known bugs. ### 1.5.1 * Support async operations for all crud methods. * Add **saveOrUpdate()** method in DataSupport. * Fix known bugs. ### 1.4.1 * Support multiple databases. * Support crud operations for generic collection data in models. * Add SQLite keywords convert function to avoid keywords conflict. * Fix bug of DateSupport.count error. * Fix bug of losing blob data when upgrading database. * Fix other known bugs. ### 1.3.2 * Improve an outstanding speed up of querying and saving. * Support to store database file in external storage. * Support to mapping fields which inherit from superclass. * Add **findFirst()** and **findLast()** in fluent query. * Add **isExist()** and **saveIfNotExist()** method in DataSupport. ### 1.3.1 * Support storing binary data. Byte array field will be mapped into database as blob type. * Add **saveFast()** method in DataSupport. If your model has no associations to handle, use **saveFast()** method will be much more efficient. * Improve query speed with optimized algorithm. ### 1.3.0 * Add annotation functions to declare **unique**, **not null** and **default** constraints. * Remove the trick of ignore mapping fields with non-private modifier. * Support to use annotation to ignore mapping fields with `ignore = true` * Add some magical methods in DataSupport for those who understand LitePal deeper. * Fix known bugs. ## License ``` Copyright (C) Lin Guo, LitePal Framework Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.5.21' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: core/.gitignore ================================================ /build ================================================ FILE: core/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { compileSdkVersion 31 defaultConfig { minSdkVersion 15 targetSdkVersion 31 archivesBaseName = "core" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { api fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: core/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/tony/Android/Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: core/src/main/AndroidManifest.xml ================================================ ================================================ FILE: core/src/main/java/org/litepal/FluentQuery.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal; import android.text.TextUtils; import org.litepal.crud.LitePalSupport; import org.litepal.crud.QueryHandler; import org.litepal.crud.async.AverageExecutor; import org.litepal.crud.async.CountExecutor; import org.litepal.crud.async.FindExecutor; import org.litepal.crud.async.FindMultiExecutor; import org.litepal.exceptions.LitePalSupportException; import org.litepal.tablemanager.Connector; import org.litepal.util.BaseUtility; import org.litepal.util.DBUtility; import java.util.List; /** * Allows developers to query tables with fluent style. * * @author Tony Green * @since 2.0 */ public class FluentQuery { /** * Representing the selected columns in SQL. */ String[] mColumns; /** * Representing the where clause in SQL. */ String[] mConditions; /** * Representing the order by clause in SQL. */ String mOrderBy; /** * Representing the limit clause in SQL. */ String mLimit; /** * Representing the offset in SQL. */ String mOffset; /** * Do not allow to create instance by developers. */ FluentQuery() { } /** * Declaring to query which columns in table. * *
	 * LitePal.select("name", "age").find(Person.class);
	 * 
* * This will find all rows with name and age columns in Person table. * * @param columns * A String array of which columns to return. Passing null will * return all columns. * * @return A ClusterQuery instance. */ public FluentQuery select(String... columns) { mColumns = columns; return this; } /** * Declaring to query which rows in table. * *
	 * LitePal.where("name = ? or age > ?", "Tom", "14").find(Person.class);
	 * 
* * This will find rows which name is Tom or age greater than 14 in Person * table. * * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return A ClusterQuery instance. */ public FluentQuery where(String... conditions) { mConditions = conditions; return this; } /** * Declaring how to order the rows queried from table. * *
	 * LitePal.order("name desc").find(Person.class);
	 * 
* * This will find all rows in Person table sorted by name with inverted * order. * * @param column * How to order the rows, formatted as an SQL ORDER BY clause. * Passing null will use the default sort order, which may be * unordered. * @return A ClusterQuery instance. */ public FluentQuery order(String column) { mOrderBy = column; return this; } /** * Limits the number of rows returned by the query. * *
	 * LitePal.limit(2).find(Person.class);
	 * 
* * This will find the top 2 rows in Person table. * * @param value * Limits the number of rows returned by the query, formatted as * LIMIT clause. * @return A ClusterQuery instance. */ public FluentQuery limit(int value) { mLimit = String.valueOf(value); return this; } /** * Declaring the offset of rows returned by the query. This method must be * used with {@link #limit(int)}, or nothing will return. * *
	 * LitePal.limit(1).offset(2).find(Person.class);
	 * 
* * This will find the third row in Person table. * * @param value * The offset amount of rows returned by the query. * @return A ClusterQuery instance. */ public FluentQuery offset(int value) { mOffset = String.valueOf(value); return this; } /** * Finds multiple records by the cluster parameters. You can use the below * way to finish a complicated query: * *
	 * LitePal.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
	 * 		.find(Person.class);
	 * 
* * You can also do the same job with SQLiteDatabase like this: * *
	 * getSQLiteDatabase().query("Person", "name", "age > ?", new String[] { "14" }, null, null, "age",
	 * 		"2,1");
	 * 
* * Obviously, the first way is much more semantic.
* Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#find(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return as a list. * @return An object list with founded data from database, or an empty list. */ public List find(Class modelClass) { return find(modelClass, false); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindMultiExecutor findAsync(final Class modelClass) { return findAsync(modelClass, false); } /** * It is mostly same as {@link FluentQuery#find(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @return An object list with founded data from database, or an empty list. */ public List find(Class modelClass, boolean isEager) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); String limit; if (mOffset == null) { limit = mLimit; } else { if (mLimit == null) { mLimit = "0"; } limit = mOffset + "," + mLimit; } return queryHandler.onFind(modelClass, mColumns, mConditions, mOrderBy, limit, isEager); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindMultiExecutor findAsync(final Class modelClass, final boolean isEager) { final FindMultiExecutor executor = new FindMultiExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final List t = find(modelClass, isEager); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Finds the first record by the cluster parameters. You can use the below * way to finish a complicated query: * *
     * LitePal.select("name").where("age > ?", "14").order("age").limit(10).offset(2)
     * 		.findFirst(Person.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#findFirst(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @return An object with founded data from database, or null. */ public T findFirst(Class modelClass) { return findFirst(modelClass, false); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor findFirstAsync(Class modelClass) { return findFirstAsync(modelClass, false); } /** * It is mostly same as {@link FluentQuery#findFirst(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with founded data from database, or null. */ public T findFirst(Class modelClass, boolean isEager) { synchronized (LitePalSupport.class) { String limitTemp = mLimit; if (!"0".equals(mLimit)) { // If mLimit not equals to 0, set mLimit to 1 to find the first record. mLimit = "1"; } List list = find(modelClass, isEager); mLimit = limitTemp; // Don't forget to change it back after finding operation. if (list.size() > 0) { if (list.size() != 1) throw new LitePalSupportException("Found multiple records while only one record should be found at most."); return list.get(0); } return null; } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor findFirstAsync(final Class modelClass, final boolean isEager) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = findFirst(modelClass, isEager); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Finds the last record by the cluster parameters. You can use the below * way to finish a complicated query: * *
     * LitePal.select("name").where("age > ?", "14").order("age").limit(10).offset(2)
     * 		.findLast(Person.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#findLast(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @return An object with founded data from database, or null. */ public T findLast(Class modelClass) { return findLast(modelClass, false); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor findLastAsync(Class modelClass) { return findLastAsync(modelClass, false); } /** * It is mostly same as {@link FluentQuery#findLast(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with founded data from database, or null. */ public T findLast(Class modelClass, boolean isEager) { synchronized (LitePalSupport.class) { String orderByTemp = mOrderBy; String limitTemp = mLimit; if (TextUtils.isEmpty(mOffset) && TextUtils.isEmpty(mLimit)) { // If mOffset or mLimit is specified, we can't use the strategy in this block to speed up finding. if (TextUtils.isEmpty(mOrderBy)) { // If mOrderBy is null, we can use id desc order, then the first record will be the record value where want to find. mOrderBy = "id desc"; } else { // If mOrderBy is not null, check if it ends with desc. if (mOrderBy.endsWith(" desc")) { // If mOrderBy ends with desc, then the last record of desc order will be the first record of asc order, so we remove the desc. mOrderBy = mOrderBy.replace(" desc", ""); } else { // If mOrderBy not ends with desc, then the last record of asc order will be the first record of desc order, so we add the desc. mOrderBy += " desc"; } } if (!"0".equals(mLimit)) { mLimit = "1"; } } List list = find(modelClass, isEager); mOrderBy = orderByTemp; mLimit = limitTemp; int size = list.size(); if (size > 0) { return list.get(size - 1); } return null; } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor findLastAsync(final Class modelClass, final boolean isEager) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = findLast(modelClass, isEager); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Count the records. * *
	 * LitePal.count(Person.class);
	 * 
* * This will count all rows in person table.
* You can also specify a where clause when counting. * *
	 * LitePal.where("age > ?", "15").count(Person.class);
	 * 
* * @param modelClass * Which table to query from by class. * @return Count of the specified table. */ public int count(Class modelClass) { return count(BaseUtility.changeCase(modelClass.getSimpleName())); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public CountExecutor countAsync(Class modelClass) { return countAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName()))); } /** * Count the records. * *
	 * LitePal.count("person");
	 * 
* * This will count all rows in person table.
* You can also specify a where clause when counting. * *
	 * LitePal.where("age > ?", "15").count("person");
	 * 
* * @param tableName * Which table to query from. * @return Count of the specified table. */ public int count(String tableName) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onCount(tableName, mConditions); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public CountExecutor countAsync(final String tableName) { final CountExecutor executor = new CountExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int count = count(tableName); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(count); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the average value on a given column. * *
	 * LitePal.average(Person.class, "age");
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").average(Person.class, "age");
	 * 
* * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return The average value on a given column. */ public double average(Class modelClass, String column) { return average(BaseUtility.changeCase(modelClass.getSimpleName()), column); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public AverageExecutor averageAsync(final Class modelClass, final String column) { return averageAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column); } /** * Calculates the average value on a given column. * *
	 * LitePal.average("person", "age");
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").average("person", "age");
	 * 
* * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return The average value on a given column. */ public double average(String tableName, String column) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onAverage(tableName, column, mConditions); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public AverageExecutor averageAsync(final String tableName, final String column) { final AverageExecutor executor = new AverageExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final double average = average(tableName, column); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(average); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * *
	 * LitePal.max(Person.class, "age", int.class);
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
	 * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ public T max(Class modelClass, String columnName, Class columnType) { return max(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor maxAsync(final Class modelClass, final String columnName, final Class columnType) { return maxAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * *
	 * LitePal.max("person", "age", int.class);
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").max("person", "age", Integer.TYPE);
	 * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ public T max(String tableName, String columnName, Class columnType) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onMax(tableName, columnName, mConditions, columnType); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor maxAsync(final String tableName, final String columnName, final Class columnType) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = max(tableName, columnName, columnType); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * *
	 * LitePal.min(Person.class, "age", int.class);
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
	 * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ public T min(Class modelClass, String columnName, Class columnType) { return min(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor minAsync(final Class modelClass, final String columnName, final Class columnType) { return minAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * *
	 * LitePal.min("person", "age", int.class);
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").min("person", "age", Integer.TYPE);
	 * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ public T min(String tableName, String columnName, Class columnType) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onMin(tableName, columnName, mConditions, columnType); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor minAsync(final String tableName, final String columnName, final Class columnType) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = min(tableName, columnName, columnType); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * *
	 * LitePal.sum(Person.class, "age", int.class);
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
	 * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ public T sum(Class modelClass, String columnName, Class columnType) { return sum(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor sumAsync(final Class modelClass, final String columnName, final Class columnType) { return sumAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * *
	 * LitePal.sum("person", "age", int.class);
	 * 
* * You can also specify a where clause when calculating. * *
	 * LitePal.where("age > ?", "15").sum("person", "age", Integer.TYPE);
	 * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ public T sum(String tableName, String columnName, Class columnType) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onSum(tableName, columnName, mConditions, columnType); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public FindExecutor sumAsync(final String tableName, final String columnName, final Class columnType) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = sum(tableName, columnName, columnType); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } } ================================================ FILE: core/src/main/java/org/litepal/LitePal.kt ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteDatabase import org.litepal.crud.LitePalSupport import org.litepal.tablemanager.callback.DatabaseListener /** * LitePal is an Android library that allows developers to use SQLite database extremely easy. * You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to * work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} * methods. * * @author Tony Green * @since 2.0 */ object LitePal { /** * Initialize to make LitePal ready to work. If you didn't configure LitePalApplication * in the AndroidManifest.xml, make sure you call this method as soon as possible. In * Application's onCreate() method will be fine. * * @param context * Application context. */ @JvmStatic fun initialize(context: Context) { Operator.initialize(context) } /** * Get a writable SQLiteDatabase. * * @return A writable SQLiteDatabase instance */ @JvmStatic fun getDatabase(): SQLiteDatabase = Operator.getDatabase() /** * Begins a transaction in EXCLUSIVE mode. */ @JvmStatic fun beginTransaction() = Operator.beginTransaction() /** * End a transaction. */ @JvmStatic fun endTransaction() = Operator.endTransaction() /** * Marks the current transaction as successful. Do not do any more database work between calling this and calling endTransaction. * Do as little non-database work as possible in that situation too. * If any errors are encountered between this and endTransaction the transaction will still be committed. */ @JvmStatic fun setTransactionSuccessful() = Operator.setTransactionSuccessful() /** * Switch the using database to the one specified by parameter. * @param litePalDB * The database to switch to. */ @JvmStatic fun use(litePalDB: LitePalDB) { Operator.use(litePalDB) } /** * Switch the using database to default with configuration by litepal.xml. */ @JvmStatic fun useDefault() { Operator.useDefault() } /** * Delete the specified database. * @param dbName * Name of database to delete. * @return True if delete success, false otherwise. */ @JvmStatic fun deleteDatabase(dbName: String) = Operator.deleteDatabase(dbName) @JvmStatic fun aesKey(key: String) { Operator.aesKey(key) } /** * Declaring to query which columns in table. * * LitePal.select("name", "age").find(Person.class); * * This will find all rows with name and age columns in Person table. * * @param columns * A String array of which columns to return. Passing null will * return all columns. * * @return A FluentQuery instance. */ @JvmStatic fun select(vararg columns: String?) = Operator.select(*columns) /** * Declaring to query which rows in table. * * LitePal.where("name = ? or age > ?", "Tom", "14").find(Person.class); * * This will find rows which name is Tom or age greater than 14 in Person * table. * * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return A FluentQuery instance. */ @JvmStatic fun where(vararg conditions: String?) = Operator.where(*conditions) /** * Declaring how to order the rows queried from table. * * LitePal.order("name desc").find(Person.class); * * This will find all rows in Person table sorted by name with inverted * order. * * @param column * How to order the rows, formatted as an SQL ORDER BY clause. * Passing null will use the default sort order, which may be * unordered. * @return A FluentQuery instance. */ @JvmStatic fun order(column: String?) = Operator.order(column) /** * Limits the number of rows returned by the query. * * LitePal.limit(2).find(Person.class); * * This will find the top 2 rows in Person table. * * @param value * Limits the number of rows returned by the query, formatted as * LIMIT clause. * @return A FluentQuery instance. */ @JvmStatic fun limit(value: Int) = Operator.limit(value) /** * Declaring the offset of rows returned by the query. This method must be * used with [LitePal.limit], or nothing will return. * * LitePal.limit(1).offset(2).find(Person.class); * * This will find the third row in Person table. * * @param value * The offset amount of rows returned by the query. * @return A FluentQuery instance. */ @JvmStatic fun offset(value: Int) = Operator.offset(value) /** * Count the records. * * LitePal.count(Person.class); * * This will count all rows in person table. * * You can also specify a where clause when counting. * * LitePal.where("age > ?", "15").count(Person.class); * * @param modelClass * Which table to query from by class. * @return Count of the specified table. */ @JvmStatic fun count(modelClass: Class<*>) = Operator.count(modelClass) /** * Basically same as [LitePal.count] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @return A CountExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun countAsync(modelClass: Class<*>) = Operator.countAsync(modelClass) /** * Count the records. * * LitePal.count("person"); * * This will count all rows in person table. * * You can also specify a where clause when counting. * * LitePal.where("age > ?", "15").count("person"); * * @param tableName * Which table to query from. * @return Count of the specified table. */ @JvmStatic fun count(tableName: String) = Operator.count(tableName) /** * Basically same as [LitePal.count] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @return A CountExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun countAsync(tableName: String) = Operator.countAsync(tableName) /** * Calculates the average value on a given column. * * LitePal.average(Person.class, "age"); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").average(Person.class, "age"); * * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return The average value on a given column. */ @JvmStatic fun average(modelClass: Class<*>, column: String) = Operator.average(modelClass, column) /** * Basically same as [LitePal.average] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun averageAsync(modelClass: Class<*>, column: String) = Operator.averageAsync(modelClass, column) /** * Calculates the average value on a given column. * * LitePal.average("person", "age"); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").average("person", "age"); * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return The average value on a given column. */ @JvmStatic fun average(tableName: String, column: String) = Operator.average(tableName, column) /** * Basically same as [LitePal.average] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun averageAsync(tableName: String, column: String) = Operator.averageAsync(tableName, column) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max(Person.class, "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max(Person.class, "age", Integer.TYPE); * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ @JvmStatic fun max(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.max(modelClass, columnName, columnType) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun maxAsync(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.maxAsync(modelClass, columnName, columnType) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max("person", "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max("person", "age", Integer.TYPE); * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ @JvmStatic fun max(tableName: String, columnName: String, columnType: Class) = Operator.max(tableName, columnName, columnType) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun maxAsync(tableName: String, columnName: String, columnType: Class) = Operator.maxAsync(tableName, columnName, columnType) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min(Person.class, "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min(Person.class, "age", Integer.TYPE); * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ @JvmStatic fun min(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.min(modelClass, columnName, columnType) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun minAsync(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.minAsync(modelClass, columnName, columnType) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min("person", "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min("person", "age", Integer.TYPE); * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ @JvmStatic fun min(tableName: String, columnName: String, columnType: Class) = Operator.min(tableName, columnName, columnType) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun minAsync(tableName: String, columnName: String, columnType: Class) = Operator.minAsync(tableName, columnName, columnType) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum(Person.class, "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE); * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ @JvmStatic fun sum(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.sum(modelClass, columnName, columnType) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun sumAsync(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.sumAsync(modelClass, columnName, columnType) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum("person", "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum("person", "age", Integer.TYPE); * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ @JvmStatic fun sum(tableName: String, columnName: String, columnType: Class) = Operator.sum(tableName, columnName, columnType) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun sumAsync(tableName: String, columnName: String, columnType: Class) = Operator.sumAsync(tableName, columnName, columnType) /** * Finds the record by a specific id. * * Person p = LitePal.find(Person.class, 1); * * The modelClass determines which table to query and the object type to * return. If no record can be found, then return null. * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.find]. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return An object with found data from database, or null. */ @JvmStatic fun find(modelClass: Class, id: Long) = Operator.find(modelClass, id) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findAsync(modelClass: Class, id: Long) = Operator.findAsync(modelClass, id) /** * It is mostly same as [LitePal.find] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ @JvmStatic fun find(modelClass: Class, id: Long, isEager: Boolean) = Operator.find(modelClass, id, isEager) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findAsync(modelClass: Class, id: Long, isEager: Boolean) = Operator.findAsync(modelClass, id, isEager) /** * Finds the first record of a single table. * * Person p = LitePal.findFirst(Person.class); * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findFirst]. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of first row, or null. */ @JvmStatic fun findFirst(modelClass: Class) = Operator.findFirst(modelClass) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findFirstAsync(modelClass: Class) = Operator.findFirstAsync(modelClass) /** * It is mostly same as [LitePal.findFirst] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ @JvmStatic fun findFirst(modelClass: Class, isEager: Boolean) = Operator.findFirst(modelClass, isEager) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findFirstAsync(modelClass: Class, isEager: Boolean) = Operator.findFirstAsync(modelClass, isEager) /** * Finds the last record of a single table. * * Person p = LitePal.findLast(Person.class); * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findLast]. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of last row, or null. */ @JvmStatic fun findLast(modelClass: Class) = Operator.findLast(modelClass) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findLastAsync(modelClass: Class) = Operator.findLastAsync(modelClass) /** * It is mostly same as [LitePal.findLast] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ @JvmStatic fun findLast(modelClass: Class, isEager: Boolean) = Operator.findLast(modelClass, isEager) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findLastAsync(modelClass: Class, isEager: Boolean) = Operator.findLastAsync(modelClass, isEager) /** * Finds multiple records by an id array. * * List<Person> people = LitePal.findAll(Person.class, 1, 2, 3); * * long[] bookIds = { 10, 18 }; * List<Book> books = LitePal.findAll(Book.class, bookIds); * * Of course you can find all records by passing nothing to the ids * parameter. * * List<Book> allBooks = LitePal.findAll(Book.class); * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findAll]. * * The modelClass determines which table to query and the object type to * return. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ @JvmStatic fun findAll(modelClass: Class, vararg ids: Long) = Operator.findAll(modelClass, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findAllAsync(modelClass: Class, vararg ids: Long) = Operator.findAllAsync(modelClass, *ids) /** * It is mostly same as [LitePal.findAll] but an * isEager parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ @JvmStatic fun findAll(modelClass: Class, isEager: Boolean, vararg ids: Long) = Operator.findAll(modelClass, isEager, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun findAllAsync(modelClass: Class, isEager: Boolean, vararg ids: Long) = Operator.findAllAsync(modelClass, isEager, *ids) /** * Runs the provided SQL and returns a Cursor over the result set. You may * include ? in where clause in the query, which will be replaced by the * second to the last parameters, such as: * * Cursor cursor = LitePal.findBySQL("select * from person where name=? and age=?", "Tom", "14"); * * @param sql * First parameter is the SQL clause to apply. Second to the last * parameters will replace the place holders. * @return A Cursor object, which is positioned before the first entry. Note * that Cursors are not synchronized, see the documentation for more * details. */ @JvmStatic fun findBySQL(vararg sql: String) = Operator.findBySQL(*sql) /** * Deletes the record in the database by id. * * The data in other tables which is referenced with the record will be * removed too. * * LitePal.delete(Person.class, 1); * * This means that the record 1 in person table will be removed. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ @JvmStatic fun delete(modelClass: Class<*>, id: Long) = Operator.delete(modelClass, id) /** * Basically same as [LitePal.delete] but pending to a new thread for executing. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun deleteAsync(modelClass: Class<*>, id: Long) = Operator.deleteAsync(modelClass, id) /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * * LitePal.deleteAll(Person.class, "name = ? and age = ?", "Tom", "14"); * * This means that all the records which name is Tom and age is 14 will be * removed. * * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return The number of rows affected. */ @JvmStatic fun deleteAll(modelClass: Class<*>, vararg conditions: String?) = Operator.deleteAll(modelClass, *conditions) /** * Basically same as [LitePal.deleteAll] but pending to a new thread for executing. * * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun deleteAllAsync(modelClass: Class<*>, vararg conditions: String?) = Operator.deleteAllAsync(modelClass, *conditions) /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * * LitePal.deleteAll("person", "name = ? and age = ?", "Tom", "14"); * * This means that all the records which name is Tom and age is 14 will be * removed. * * Note that this method won't delete the referenced data in other tables. * You should remove those values by your own. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return The number of rows affected. */ @JvmStatic fun deleteAll(tableName: String, vararg conditions: String?) = Operator.deleteAll(tableName, *conditions) /** * Basically same as [LitePal.deleteAll] but pending to a new thread for executing. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun deleteAllAsync(tableName: String, vararg conditions: String?) = Operator.deleteAllAsync(tableName, *conditions) /** * Updates the corresponding record by id with ContentValues. Returns the * number of affected rows. * * ContentValues cv = new ContentValues(); * * cv.put("name", "Jim"); * * LitePal.update(Person.class, cv, 1); * * This means that the name of record 1 will be updated into Jim. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return The number of rows affected. */ @JvmStatic fun update(modelClass: Class<*>, values: ContentValues, id: Long) = Operator.update(modelClass, values, id) /** * Basically same as [LitePal.update] but pending to a new thread for executing. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun updateAsync(modelClass: Class<*>, values: ContentValues, id: Long) = Operator.updateAsync(modelClass, values, id) /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * * ContentValues cv = new ContentValues(); * * cv.put("name", "Jim"); * * LitePal.update(Person.class, cv, "name = ?", "Tom"); * * This means that all the records which name is Tom will be updated into * Jim. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ @JvmStatic fun updateAll(modelClass: Class<*>, values: ContentValues, vararg conditions: String?) = Operator.updateAll(modelClass, values, *conditions) /** * Basically same as [LitePal.updateAll] but pending to a new thread for executing. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun updateAllAsync(modelClass: Class<*>, values: ContentValues, vararg conditions: String?) = Operator.updateAllAsync(modelClass, values, *conditions) /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * * ContentValues cv = new ContentValues(); * * cv.put("name", "Jim"); * * LitePal.update("person", cv, "name = ?", "Tom"); * * This means that all the records which name is Tom will be updated into * Jim. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ @JvmStatic fun updateAll(tableName: String, values: ContentValues, vararg conditions: String?) = Operator.updateAll(tableName, values, *conditions) /** * Basically same as [LitePal.updateAll] but pending to a new thread for executing. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun updateAllAsync(tableName: String, values: ContentValues, vararg conditions: String?) = Operator.updateAllAsync(tableName, values, *conditions) /** * Saves the collection into database. * * LitePal.saveAll(people); * * If the model in collection is a new record gets created in the database, * otherwise the existing record gets updated. * * If saving process failed by any accident, the whole action will be * cancelled and your database will be **rolled back**. * * This method acts the same result as the below way, but **much more * efficient**. * * for (Person person : people) { * person.save(); * } * * So when your collection holds huge of models, saveAll(Collection) is the better choice. * * @param collection * Holds all models to save. * @return True if all records in collection are saved. False none record in collection is saved. There won't be partial saved condition. */ @JvmStatic fun saveAll(collection: Collection) = Operator.saveAll(collection) /** * Basically same as [LitePal.saveAll] but pending to a new thread for executing. * * @param collection * Holds all models to save. * @return A SaveExecutor instance. */ @JvmStatic @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) fun saveAllAsync(collection: Collection) = Operator.saveAllAsync(collection) /** * Provide a way to mark all models in collection as deleted. This means these models' save * state is no longer exist anymore. If save them again, they will be treated as inserting new * data instead of updating the exist one. * @param collection * Collection of models which want to mark as deleted and clear their save state. */ @JvmStatic fun markAsDeleted(collection: Collection) { Operator.markAsDeleted(collection) } /** * Check if the specified conditions data already exists in the table. * @param modelClass * Which table to check by class. * @param conditions * A filter declaring which data to check. Exactly same use as * [LitePal.where], except null conditions will result in false. * @return Return true if the specified conditions data already exists in the table. * False otherwise. Null conditions will result in false. */ @JvmStatic fun isExist(modelClass: Class, vararg conditions: String?) = Operator.isExist(modelClass, *conditions) /** * Register a listener to listen database create and upgrade events. */ @JvmStatic fun registerDatabaseListener(listener: DatabaseListener) { Operator.registerDatabaseListener(listener) } } ================================================ FILE: core/src/main/java/org/litepal/LitePalApplication.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal; import org.litepal.exceptions.GlobalException; import android.annotation.SuppressLint; import android.app.Application; import android.content.Context; import android.os.Handler; import android.os.Looper; /** * Base class of LitePal to make things easier when developers need to use * context. When you need context, just use * LitePalApplication.getContext(). To make this function work, you need * to configure your AndroidManifest.xml. Specifying * "org.litepal.LitePalApplication" as the application name in your * <application> tag to enable LitePal get the context. Of course if you * need to write your own Application class, LitePal can still live with that. * But just remember make your own Application class inherited from * LitePalApplication instead of inheriting from Application directly. This can * make all things work without side effects.
* Besides if you don't want use the above way, you can also call the LitePal.initialize(Context) * method to do the same job. Just remember call this method as early as possible, in Application's onCreate() * method will be fine. * * @author Tony Green * @since 1.0 */ public class LitePalApplication extends Application { /** * Global application context. */ @SuppressLint("StaticFieldLeak") public static Context sContext; public static Handler sHandler = new Handler(Looper.getMainLooper()); /** * Construct of LitePalApplication. Initialize application context. */ public LitePalApplication() { sContext = this; } /** * Get the global application context. * * @return Application context. * @throws org.litepal.exceptions.GlobalException */ public static Context getContext() { if (sContext == null) { throw new GlobalException(GlobalException.APPLICATION_CONTEXT_IS_NULL); } return sContext; } } ================================================ FILE: core/src/main/java/org/litepal/LitePalBase.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal; import org.litepal.annotation.Column; import org.litepal.crud.LitePalSupport; import org.litepal.crud.model.AssociationsInfo; import org.litepal.exceptions.DatabaseGenerateException; import org.litepal.parser.LitePalAttr; import org.litepal.tablemanager.model.AssociationsModel; import org.litepal.tablemanager.model.ColumnModel; import org.litepal.tablemanager.model.GenericModel; import org.litepal.tablemanager.model.TableModel; import org.litepal.tablemanager.typechange.BlobOrm; import org.litepal.tablemanager.typechange.BooleanOrm; import org.litepal.tablemanager.typechange.DateOrm; import org.litepal.tablemanager.typechange.DecimalOrm; import org.litepal.tablemanager.typechange.NumericOrm; import org.litepal.tablemanager.typechange.OrmChange; import org.litepal.tablemanager.typechange.TextOrm; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.DBUtility; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Base class of all the LitePal components. If each component need to * interactive with other components or they have some same logic with duplicate * codes, LitePalBase may be the solution. * * @author Tony Green * @since 1.1 */ public abstract class LitePalBase { public static final String TAG = "LitePalBase"; /** * Action to get associations. */ private static final int GET_ASSOCIATIONS_ACTION = 1; /** * Action to get association info. */ private static final int GET_ASSOCIATION_INFO_ACTION = 2; /** * All the supporting mapping types currently in the array. */ private OrmChange[] typeChangeRules = { new NumericOrm(), new TextOrm(), new BooleanOrm(), new DecimalOrm(), new DateOrm(), new BlobOrm()}; /** * This is map of class name to fields list. Indicates that each class has which supported fields. */ private Map> classFieldsMap = new HashMap<>(); /** * This is map of class name to generic fields list. Indicates that each class has which supported generic fields. */ private Map> classGenericFieldsMap = new HashMap<>(); /** * The collection contains all association models. */ private Collection mAssociationModels; /** * The collection contains all association info. */ private Collection mAssociationInfos; /** * The collection contains all generic models. */ private Collection mGenericModels; /** * This method is used to get the table model by the class name passed * in. The principle to generate table model is that each field in the class * with non-static modifier and has a type among int/Integer, long/Long, * short/Short, float/Float, double/Double, char/Character, boolean/Boolean * or String, would generate a column with same name as corresponding field. * If users don't want some of the fields map a column, declare an ignore * annotation with {@link Column#ignore()}. * * @param className * The full name of the class to map in database. * @return A table model with table name, class name and the map of column * name and column type. */ protected TableModel getTableModel(String className) { String tableName = DBUtility.getTableNameByClassName(className); TableModel tableModel = new TableModel(); tableModel.setTableName(tableName); tableModel.setClassName(className); List supportedFields = getSupportedFields(className); for (Field field : supportedFields) { ColumnModel columnModel = convertFieldToColumnModel(field); tableModel.addColumnModel(columnModel); } return tableModel; } /** * This method is used to get association models depends on the given class * name list. * * @param classNames * The names of the classes that want to get their associations. * @return Collection of association models. */ protected Collection getAssociations(List classNames) { if (mAssociationModels == null) { mAssociationModels = new HashSet<>(); } if (mGenericModels == null) { mGenericModels = new HashSet<>(); } mAssociationModels.clear(); mGenericModels.clear(); for (String className : classNames) { analyzeClassFields(className, GET_ASSOCIATIONS_ACTION); } return mAssociationModels; } /** * Get all generic models for create generic tables. * @return All generic models. */ protected Collection getGenericModels() { return mGenericModels; } /** * Get the association info model by the class name. * * @param className * The class name to introspection. * @return Collection of association info. */ protected Collection getAssociationInfo(String className) { if (mAssociationInfos == null) { mAssociationInfos = new HashSet<>(); } mAssociationInfos.clear(); analyzeClassFields(className, GET_ASSOCIATION_INFO_ACTION); return mAssociationInfos; } /** * Find all the fields in the class. But not each field is supported to add * a column to the table. Only the basic data types and String are * supported. This method will intercept all the types which are not * supported and return a new list of supported fields. * * @param className * The full name of the class. * @return A list of supported fields. */ protected List getSupportedFields(String className) { List fieldList = classFieldsMap.get(className); if (fieldList == null) { List supportedFields = new ArrayList<>(); Class clazz; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); } recursiveSupportedFields(clazz, supportedFields); classFieldsMap.put(className, supportedFields); return supportedFields; } return fieldList; } /** * Find all supported generic fields in the class. Supporting rule is in {@link BaseUtility#isGenericTypeSupported(String)}. * @param className * The full name of the class. * @return A list of supported generic fields. */ protected List getSupportedGenericFields(String className) { List genericFieldList = classGenericFieldsMap.get(className); if (genericFieldList == null) { List supportedGenericFields = new ArrayList<>(); Class clazz; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); } recursiveSupportedGenericFields(clazz, supportedGenericFields); classGenericFieldsMap.put(className, supportedGenericFields); return supportedGenericFields; } return genericFieldList; } /** * If the field type implements from List or Set, regard it as a collection. * * @param fieldType * The field type. * @return True if the field type is collection, false otherwise. */ protected boolean isCollection(Class fieldType) { return isList(fieldType) || isSet(fieldType); } /** * If the field type implements from List, regard it as a list. * * @param fieldType * The field type. * @return True if the field type is List, false otherwise. */ protected boolean isList(Class fieldType) { return List.class.isAssignableFrom(fieldType); } /** * If the field type implements from Set, regard it as a set. * * @param fieldType * The field type. * @return True if the field type is Set, false otherwise. */ protected boolean isSet(Class fieldType) { return Set.class.isAssignableFrom(fieldType); } /** * Judge the passed in column is an id column or not. The column named id or * _id will be considered as id column. * * @param columnName * The name of column. * @return Return true if it's id column, otherwise return false. */ protected boolean isIdColumn(String columnName) { return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName); } /** * If two tables are associated, one table have a foreign key column. The * foreign key column name will be the associated table name with _id * appended. * * @param associatedTableName * The associated table name. * @return The foreign key column name. */ protected String getForeignKeyColumnName(String associatedTableName) { return BaseUtility.changeCase(associatedTableName + "_id"); } /** * Get the column type for creating table by field type. * @param fieldType * Type of field. * @return The column type for creating table. */ protected String getColumnType(String fieldType) { String columnType; for (OrmChange ormChange : typeChangeRules) { columnType = ormChange.object2Relation(fieldType); if (columnType != null) { return columnType; } } return null; } /** * Get the generic type class of List or Set. If there's no generic type of * List or Set return null. * * @param field * A generic type field. * @return The generic type of List or Set. */ protected Class getGenericTypeClass(Field field) { Type genericType = field.getGenericType(); if (genericType != null) { if (genericType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericType; return (Class) parameterizedType.getActualTypeArguments()[0]; } } return null; } private void recursiveSupportedFields(Class clazz, List supportedFields) { if (clazz == LitePalSupport.class || clazz == Object.class) { return; } Field[] fields = clazz.getDeclaredFields(); if (fields.length > 0) { for (Field field : fields) { Column annotation = field.getAnnotation(Column.class); if (annotation != null && annotation.ignore()) { continue; } int modifiers = field.getModifiers(); if (!Modifier.isStatic(modifiers)) { Class fieldTypeClass = field.getType(); String fieldType = fieldTypeClass.getName(); if (BaseUtility.isFieldTypeSupported(fieldType)) { supportedFields.add(field); } } } } recursiveSupportedFields(clazz.getSuperclass(), supportedFields); } private void recursiveSupportedGenericFields(Class clazz, List supportedGenericFields) { if (clazz == LitePalSupport.class || clazz == Object.class) { return; } Field[] fields = clazz.getDeclaredFields(); if (fields.length > 0) { for (Field field : fields) { Column annotation = field.getAnnotation(Column.class); if (annotation != null && annotation.ignore()) { continue; } int modifiers = field.getModifiers(); if (!Modifier.isStatic(modifiers) && isCollection(field.getType())) { String genericTypeName = getGenericTypeName(field); if (BaseUtility.isGenericTypeSupported(genericTypeName) || clazz.getName().equalsIgnoreCase(genericTypeName)) { supportedGenericFields.add(field); } } } } recursiveSupportedGenericFields(clazz.getSuperclass(), supportedGenericFields); } /** * Introspection of the passed in class. Analyze the fields of current class * and find out the associations of it. * * @param className * The class name to introspection. * @param action * Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and * {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION} */ private void analyzeClassFields(String className, int action) { try { Class dynamicClass = Class.forName(className); Field[] fields = dynamicClass.getDeclaredFields(); for (Field field : fields) { if (isNonPrimitive(field)) { Column annotation = field.getAnnotation(Column.class); if (annotation != null && annotation.ignore()) { continue; } oneToAnyConditions(className, field, action); manyToAnyConditions(className, field, action); } } } catch (ClassNotFoundException ex) { ex.printStackTrace(); throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); } } /** * Check the field is a non primitive field or not. * * @param field * The field to check. * @return True if the field is non primitive, false otherwise. */ private boolean isNonPrimitive(Field field) { return !field.getType().isPrimitive(); } /** * Check the field is a private field or not. * * @param field * The field to check. * @return True if the field is private, false otherwise. */ protected boolean isPrivate(Field field) { return Modifier.isPrivate(field.getModifiers()); } /** * Deals with one to any association conditions. e.g. Song and Album. An * album have many songs, and a song belongs to one album. So if there's an * Album model defined in Song with private modifier, and in Album there's a * List or Set with generic type of Song and declared as private modifier, * they are one2many association. If there's no List or Set defined in * Album, they will become one2one associations. If there's also a Song * model defined in Album with private modifier, maybe the album just have * one song, they are one2one association too. * * When it's many2one association, it's easy to just simply add a foreign id * column to the many side model's table. But when it comes to many2many * association, it can not be done without intermediate join table in * database. LitePal assumes that this join table's name is the * concatenation of the two target table names in alphabetical order. * * @param className * Source class name. * @param field * A field of source class. * @param action * Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and * {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION} */ private void oneToAnyConditions(String className, Field field, int action) throws ClassNotFoundException { Class fieldTypeClass = field.getType(); // If the mapping list contains the class name // defined in one class. if (LitePalAttr.getInstance().getClassNames().contains(fieldTypeClass.getName())) { Class reverseDynamicClass = Class.forName(fieldTypeClass.getName()); Field[] reverseFields = reverseDynamicClass.getDeclaredFields(); // Look up if there's a reverse association // definition in the reverse class. boolean reverseAssociations = false; // Begin to check the fields of the defined // class. for (Field reverseField : reverseFields) { if (!Modifier.isStatic(reverseField.getModifiers())) { Class reverseFieldTypeClass = reverseField.getType(); // If there's the from class name in the // defined class, they are one2one bidirectional // associations. if (className.equals(reverseFieldTypeClass.getName())) { if (action == GET_ASSOCIATIONS_ACTION) { addIntoAssociationModelCollection(className, fieldTypeClass.getName(), fieldTypeClass.getName(), Const.Model.ONE_TO_ONE); } else if (action == GET_ASSOCIATION_INFO_ACTION) { addIntoAssociationInfoCollection(className, fieldTypeClass.getName(), fieldTypeClass.getName(), field, reverseField, Const.Model.ONE_TO_ONE); } reverseAssociations = true; } // If there's the from class Set or List in // the defined class, they are many2one bidirectional // associations. else if (isCollection(reverseFieldTypeClass)) { String genericTypeName = getGenericTypeName(reverseField); if (className.equals(genericTypeName)) { if (action == GET_ASSOCIATIONS_ACTION) { addIntoAssociationModelCollection(className, fieldTypeClass.getName(), className, Const.Model.MANY_TO_ONE); } else if (action == GET_ASSOCIATION_INFO_ACTION) { addIntoAssociationInfoCollection(className, fieldTypeClass.getName(), className, field, reverseField, Const.Model.MANY_TO_ONE); } reverseAssociations = true; } } } } // If there's no from class in the defined class, they are // one2one unidirectional associations. if (!reverseAssociations) { if (action == GET_ASSOCIATIONS_ACTION) { addIntoAssociationModelCollection(className, fieldTypeClass.getName(), fieldTypeClass.getName(), Const.Model.ONE_TO_ONE); } else if (action == GET_ASSOCIATION_INFO_ACTION) { addIntoAssociationInfoCollection(className, fieldTypeClass.getName(), fieldTypeClass.getName(), field, null, Const.Model.ONE_TO_ONE); } } } } /** * Deals with one to any association conditions. e.g. Song and Album. An * album have many songs, and a song belongs to one album. So if there's an * Album model defined in Song with private modifier, and in Album there's a * List or Set with generic type of Song and declared as private modifier, * they are one2many association. If there's no List or Set defined in * Album, they will become one2one associations. If there's also a Song * model defined in Album with private modifier, maybe the album just have * one song, they are one2one association too. * * When it's many2one association, it's easy to just simply add a foreign id * column to the many side model's table. But when it comes to many2many * association, it can not be done without intermediate join table in * database. LitePal assumes that this join table's name is the * concatenation of the two target table names in alphabetical order. * * @param className * Source class name. * @param field * A field of source class. * @param action * Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and * {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION} */ private void manyToAnyConditions(String className, Field field, int action) throws ClassNotFoundException { if (isCollection(field.getType())) { String genericTypeName = getGenericTypeName(field); // If the mapping list contains the genericTypeName, begin to check // this genericTypeName class. if (LitePalAttr.getInstance().getClassNames().contains(genericTypeName)) { Class reverseDynamicClass = Class.forName(genericTypeName); Field[] reverseFields = reverseDynamicClass.getDeclaredFields(); // Look up if there's a reverse association // definition in the reverse class. boolean reverseAssociations = false; for (Field reverseField : reverseFields) { // Only map private fields if (!Modifier.isStatic(reverseField.getModifiers())) { Class reverseFieldTypeClass = reverseField.getType(); // If there's a from class name defined in the reverse // class, they are many2one bidirectional // associations. if (className.equals(reverseFieldTypeClass.getName())) { if (action == GET_ASSOCIATIONS_ACTION) { addIntoAssociationModelCollection(className, genericTypeName, genericTypeName, Const.Model.MANY_TO_ONE); } else if (action == GET_ASSOCIATION_INFO_ACTION) { addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName, field, reverseField, Const.Model.MANY_TO_ONE); } reverseAssociations = true; } // If there's a List or Set contains from class name // defined in the reverse class, they are many2many // association. else if (isCollection(reverseFieldTypeClass)) { String reverseGenericTypeName = getGenericTypeName(reverseField); if (className.equals(reverseGenericTypeName)) { if (action == GET_ASSOCIATIONS_ACTION) { if (className.equalsIgnoreCase(genericTypeName)) { // This is M2M self association condition. Regard as generic model condition. GenericModel genericModel = new GenericModel(); genericModel.setTableName(DBUtility.getGenericTableName(className, field.getName())); genericModel.setValueColumnName(DBUtility.getM2MSelfRefColumnName(field)); genericModel.setValueColumnType("integer"); genericModel.setValueIdColumnName(DBUtility.getGenericValueIdColumnName(className)); mGenericModels.add(genericModel); } else { addIntoAssociationModelCollection(className, genericTypeName, null, Const.Model.MANY_TO_MANY); } } else if (action == GET_ASSOCIATION_INFO_ACTION) { if (!className.equalsIgnoreCase(genericTypeName)) { addIntoAssociationInfoCollection(className, genericTypeName, null, field, reverseField, Const.Model.MANY_TO_MANY); } } reverseAssociations = true; } } } } // If there's no from class in the defined class, they // are many2one unidirectional associations. if (!reverseAssociations) { if (action == GET_ASSOCIATIONS_ACTION) { addIntoAssociationModelCollection(className, genericTypeName, genericTypeName, Const.Model.MANY_TO_ONE); } else if (action == GET_ASSOCIATION_INFO_ACTION) { addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName, field, null, Const.Model.MANY_TO_ONE); } } } else if(BaseUtility.isGenericTypeSupported(genericTypeName) && action == GET_ASSOCIATIONS_ACTION) { GenericModel genericModel = new GenericModel(); genericModel.setTableName(DBUtility.getGenericTableName(className, field.getName())); genericModel.setValueColumnName(DBUtility.convertToValidColumnName(field.getName())); genericModel.setValueColumnType(getColumnType(genericTypeName)); genericModel.setValueIdColumnName(DBUtility.getGenericValueIdColumnName(className)); mGenericModels.add(genericModel); } } } /** * Package a {@link org.litepal.tablemanager.model.AssociationsModel}, and add it into * {@link #mAssociationModels} Collection. * * @param className * The class name for {@link org.litepal.tablemanager.model.AssociationsModel}. * @param associatedClassName * The associated class name for {@link org.litepal.tablemanager.model.AssociationsModel}. * @param classHoldsForeignKey * The class which holds foreign key. * @param associationType * The association type for {@link org.litepal.tablemanager.model.AssociationsModel}. */ private void addIntoAssociationModelCollection(String className, String associatedClassName, String classHoldsForeignKey, int associationType) { AssociationsModel associationModel = new AssociationsModel(); associationModel.setTableName(DBUtility.getTableNameByClassName(className)); associationModel.setAssociatedTableName(DBUtility.getTableNameByClassName(associatedClassName)); associationModel.setTableHoldsForeignKey(DBUtility.getTableNameByClassName(classHoldsForeignKey)); associationModel.setAssociationType(associationType); mAssociationModels.add(associationModel); } /** * Package a {@link org.litepal.crud.model.AssociationsInfo}, and add it into * {@link #mAssociationInfos} Collection. * * @param selfClassName * The class name of self model. * @param associatedClassName * The class name of the class which associated with self class. * @param classHoldsForeignKey * The class which holds foreign key. * @param associateOtherModelFromSelf * The field of self class to declare has association with other * class. * @param associateSelfFromOtherModel * The field of the associated class to declare has association * with self class. * @param associationType * The association type. */ private void addIntoAssociationInfoCollection(String selfClassName, String associatedClassName, String classHoldsForeignKey, Field associateOtherModelFromSelf, Field associateSelfFromOtherModel, int associationType) { AssociationsInfo associationInfo = new AssociationsInfo(); associationInfo.setSelfClassName(selfClassName); associationInfo.setAssociatedClassName(associatedClassName); associationInfo.setClassHoldsForeignKey(classHoldsForeignKey); associationInfo.setAssociateOtherModelFromSelf(associateOtherModelFromSelf); associationInfo.setAssociateSelfFromOtherModel(associateSelfFromOtherModel); associationInfo.setAssociationType(associationType); mAssociationInfos.add(associationInfo); } /** * Get the generic type name of List or Set. If there's no generic type of * List or Set return null. * * @param field * A generic type field. * @return The name of generic type of List of Set. */ protected String getGenericTypeName(Field field) { Class genericTypeClass = getGenericTypeClass(field); if (genericTypeClass != null) { return genericTypeClass.getName(); } return null; } /** * Convert a field instance into A ColumnModel instance. ColumnModel can provide information * when creating table. * @param field * A supported field to map into column. * @return ColumnModel instance contains column information. */ private ColumnModel convertFieldToColumnModel(Field field) { String fieldType = field.getType().getName(); String columnType = getColumnType(fieldType); boolean nullable = true; boolean unique = false; boolean hasIndex = false; String defaultValue = ""; Column annotation = field.getAnnotation(Column.class); if (annotation != null) { nullable = annotation.nullable(); unique = annotation.unique(); defaultValue = annotation.defaultValue(); hasIndex = annotation.index(); } ColumnModel columnModel = new ColumnModel(); columnModel.setColumnName(DBUtility.convertToValidColumnName(field.getName())); columnModel.setColumnType(columnType); columnModel.setNullable(nullable); columnModel.setUnique(unique); columnModel.setDefaultValue(defaultValue); columnModel.setHasIndex(hasIndex); return columnModel; } } ================================================ FILE: core/src/main/java/org/litepal/LitePalDB.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal; import org.litepal.parser.LitePalAttr; import org.litepal.parser.LitePalConfig; import org.litepal.parser.LitePalParser; import java.util.ArrayList; import java.util.List; /** * Configuration of LitePal database. It's similar to litepal.xml configuration, but allows to * configure database details at runtime. This is very important when comes to support multiple * databases functionality. * * @author Tony Green * @since 1.4 */ public class LitePalDB { /** * The version of database. */ private int version; /** * The name of database. */ private String dbName; /** * Define where the .db file should be. Option values: internal, external, or path in sdcard. */ private String storage; /** * Indicates that the database file stores in external storage or not. */ private boolean isExternalStorage = false; /** * All the model classes that want to map in the database. Each class should * be given the full name including package name. */ private List classNames; /** * Construct a LitePalDB instance from the default configuration by litepal.xml. But database * name must be different than the default. * @param dbName * Name of database. * @return A LitePalDB instance which used the default configuration in litepal.xml but with a specified database name. */ public static LitePalDB fromDefault(String dbName) { LitePalConfig config = LitePalParser.parseLitePalConfiguration(); LitePalDB litePalDB = new LitePalDB(dbName, config.getVersion()); litePalDB.setStorage(config.getStorage()); litePalDB.setClassNames(config.getClassNames()); return litePalDB; } /** * Construct a LitePalDB instance. Database name and version are necessary fields. * @param dbName * Name of database. * @param version * Version of database. */ public LitePalDB(String dbName, int version) { this.dbName = dbName; this.version = version; } public int getVersion() { return version; } public String getDbName() { return dbName; } public String getStorage() { return storage; } public void setStorage(String storage) { this.storage = storage; } public boolean isExternalStorage() { return isExternalStorage; } public void setExternalStorage(boolean isExternalStorage) { this.isExternalStorage = isExternalStorage; } /** * Get the class name list. Always add table_schema as a value. * * @return The class name list. */ public List getClassNames() { if (classNames == null) { classNames = new ArrayList(); classNames.add("org.litepal.model.Table_Schema"); } else if (classNames.isEmpty()) { classNames.add("org.litepal.model.Table_Schema"); } return classNames; } /** * Add a class name into the current mapping model list. * * @param className * Full package class name. */ public void addClassName(String className) { getClassNames().add(className); } void setClassNames(List className) { this.classNames = className; } } ================================================ FILE: core/src/main/java/org/litepal/Operator.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import org.litepal.crud.DeleteHandler; import org.litepal.crud.LitePalSupport; import org.litepal.crud.QueryHandler; import org.litepal.crud.SaveHandler; import org.litepal.crud.UpdateHandler; import org.litepal.crud.async.AverageExecutor; import org.litepal.crud.async.CountExecutor; import org.litepal.crud.async.FindExecutor; import org.litepal.crud.async.FindMultiExecutor; import org.litepal.crud.async.SaveExecutor; import org.litepal.crud.async.UpdateOrDeleteExecutor; import org.litepal.exceptions.LitePalSupportException; import org.litepal.parser.LitePalAttr; import org.litepal.parser.LitePalConfig; import org.litepal.parser.LitePalParser; import org.litepal.tablemanager.Connector; import org.litepal.tablemanager.callback.DatabaseListener; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.DBUtility; import org.litepal.util.SharedUtil; import org.litepal.util.cipher.CipherUtil; import java.io.File; import java.util.Collection; import java.util.List; /** * LitePal is an Android library that allows developers to use SQLite database extremely easy. * You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to * work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} * methods. * * @author Tony Green * @since 2.1 */ public class Operator { private static Handler handler = new Handler(Looper.getMainLooper()); private static DatabaseListener dbListener = null; /** * Get the main thread handler. You don't need this method. It's used by framework only. * @return Main thread handler. */ public static Handler getHandler() { return handler; } /** * Initialize to make LitePal ready to work. If you didn't configure LitePalApplication * in the AndroidManifest.xml, make sure you call this method as soon as possible. In * Application's onCreate() method will be fine. * * @param context * Application context. */ public static void initialize(Context context) { LitePalApplication.sContext = context; } /** * Get a writable SQLiteDatabase. * * @return A writable SQLiteDatabase instance */ public static SQLiteDatabase getDatabase() { return Connector.getDatabase(); } /** * Begins a transaction in EXCLUSIVE mode. */ public static void beginTransaction() { getDatabase().beginTransaction(); } /** * End a transaction. */ public static void endTransaction() { getDatabase().endTransaction(); } /** * Marks the current transaction as successful. Do not do any more database work between calling this and calling endTransaction. * Do as little non-database work as possible in that situation too. * If any errors are encountered between this and endTransaction the transaction will still be committed. */ public static void setTransactionSuccessful() { getDatabase().setTransactionSuccessful(); } /** * Switch the using database to the one specified by parameter. * @param litePalDB * The database to switch to. */ public static void use(LitePalDB litePalDB) { synchronized (LitePalSupport.class) { LitePalAttr litePalAttr = LitePalAttr.getInstance(); litePalAttr.setDbName(litePalDB.getDbName()); litePalAttr.setVersion(litePalDB.getVersion()); litePalAttr.setStorage(litePalDB.getStorage()); litePalAttr.setClassNames(litePalDB.getClassNames()); // set the extra key name only when use database other than default or litepal.xml not exists if (!isDefaultDatabase(litePalDB.getDbName())) { litePalAttr.setExtraKeyName(litePalDB.getDbName()); litePalAttr.setCases("lower"); } Connector.clearLitePalOpenHelperInstance(); } } /** * Switch the using database to default with configuration by litepal.xml. */ public static void useDefault() { synchronized (LitePalSupport.class) { LitePalAttr.clearInstance(); Connector.clearLitePalOpenHelperInstance(); } } /** * Delete the specified database. * @param dbName * Name of database to delete. * @return True if delete success, false otherwise. */ public static boolean deleteDatabase(String dbName) { synchronized (LitePalSupport.class) { if (!TextUtils.isEmpty(dbName)) { if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { dbName = dbName + Const.Config.DB_NAME_SUFFIX; } File dbFile = LitePalApplication.getContext().getDatabasePath(dbName); if (dbFile.exists()) { boolean result = dbFile.delete(); if (result) { removeVersionInSharedPreferences(dbName); Connector.clearLitePalOpenHelperInstance(); } return result; } String path = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/"; dbFile = new File(path + dbName); boolean result = dbFile.delete(); if (result) { removeVersionInSharedPreferences(dbName); Connector.clearLitePalOpenHelperInstance(); } return result; } return false; } } public static void aesKey(String key) { CipherUtil.aesKey = key; } /** * Remove the database version in SharedPreferences file. * @param dbName * Name of database to delete. */ private static void removeVersionInSharedPreferences(String dbName) { if (isDefaultDatabase(dbName)) { SharedUtil.removeVersion(null); } else { SharedUtil.removeVersion(dbName); } } /** * Check the dbName is default database or not. If it's same as dbName in litepal.xml, then it is * default database. * @param dbName * Name of database to check. * @return True if it's default database, false otherwise. */ private static boolean isDefaultDatabase(String dbName) { if (BaseUtility.isLitePalXMLExists()) { if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { dbName = dbName + Const.Config.DB_NAME_SUFFIX; } LitePalConfig config = LitePalParser.parseLitePalConfiguration(); String defaultDbName = config.getDbName(); if (!defaultDbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { defaultDbName = defaultDbName + Const.Config.DB_NAME_SUFFIX; } return dbName.equalsIgnoreCase(defaultDbName); } return false; } /** * Declaring to query which columns in table. * *
     * LitePal.select("name", "age").find(Person.class);
     * 
* * This will find all rows with name and age columns in Person table. * * @param columns * A String array of which columns to return. Passing null will * return all columns. * * @return A FluentQuery instance. */ public static FluentQuery select(String... columns) { FluentQuery cQuery = new FluentQuery(); cQuery.mColumns = columns; return cQuery; } /** * Declaring to query which rows in table. * *
     * LitePal.where("name = ? or age > ?", "Tom", "14").find(Person.class);
     * 
* * This will find rows which name is Tom or age greater than 14 in Person * table. * * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return A FluentQuery instance. */ public static FluentQuery where(String... conditions) { FluentQuery cQuery = new FluentQuery(); cQuery.mConditions = conditions; return cQuery; } /** * Declaring how to order the rows queried from table. * *
     * LitePal.order("name desc").find(Person.class);
     * 
* * This will find all rows in Person table sorted by name with inverted * order. * * @param column * How to order the rows, formatted as an SQL ORDER BY clause. * Passing null will use the default sort order, which may be * unordered. * @return A FluentQuery instance. */ public static FluentQuery order(String column) { FluentQuery cQuery = new FluentQuery(); cQuery.mOrderBy = column; return cQuery; } /** * Limits the number of rows returned by the query. * *
     * LitePal.limit(2).find(Person.class);
     * 
* * This will find the top 2 rows in Person table. * * @param value * Limits the number of rows returned by the query, formatted as * LIMIT clause. * @return A FluentQuery instance. */ public static FluentQuery limit(int value) { FluentQuery cQuery = new FluentQuery(); cQuery.mLimit = String.valueOf(value); return cQuery; } /** * Declaring the offset of rows returned by the query. This method must be * used with {@link #limit(int)}, or nothing will return. * *
     * LitePal.limit(1).offset(2).find(Person.class);
     * 
* * This will find the third row in Person table. * * @param value * The offset amount of rows returned by the query. * @return A FluentQuery instance. */ public static FluentQuery offset(int value) { FluentQuery cQuery = new FluentQuery(); cQuery.mOffset = String.valueOf(value); return cQuery; } /** * Count the records. * *
     * LitePal.count(Person.class);
     * 
* * This will count all rows in person table.
* You can also specify a where clause when counting. * *
     * LitePal.where("age > ?", "15").count(Person.class);
     * 
* * @param modelClass * Which table to query from by class. * @return Count of the specified table. */ public static int count(Class modelClass) { return count(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName()))); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static CountExecutor countAsync(final Class modelClass) { return countAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName()))); } /** * Count the records. * *
     * LitePal.count("person");
     * 
* * This will count all rows in person table.
* You can also specify a where clause when counting. * *
     * LitePal.where("age > ?", "15").count("person");
     * 
* * @param tableName * Which table to query from. * @return Count of the specified table. */ public static int count(String tableName) { synchronized (LitePalSupport.class) { FluentQuery cQuery = new FluentQuery(); return cQuery.count(tableName); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static CountExecutor countAsync(final String tableName) { final CountExecutor executor = new CountExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int count = count(tableName); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(count); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the average value on a given column. * *
     * LitePal.average(Person.class, "age");
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").average(Person.class, "age");
     * 
* * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return The average value on a given column. */ public static double average(Class modelClass, String column) { return average(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static AverageExecutor averageAsync(final Class modelClass, final String column) { return averageAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column); } /** * Calculates the average value on a given column. * *
     * LitePal.average("person", "age");
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").average("person", "age");
     * 
* * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return The average value on a given column. */ public static double average(String tableName, String column) { synchronized (LitePalSupport.class) { FluentQuery cQuery = new FluentQuery(); return cQuery.average(tableName, column); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static AverageExecutor averageAsync(final String tableName, final String column) { final AverageExecutor executor = new AverageExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final double average = average(tableName, column); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(average); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.max(Person.class, "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
     * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ public static T max(Class modelClass, String columnName, Class columnType) { return max(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor maxAsync(final Class modelClass, final String columnName, final Class columnType) { return maxAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.max("person", "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").max("person", "age", Integer.TYPE);
     * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ public static T max(String tableName, String columnName, Class columnType) { synchronized (LitePalSupport.class) { FluentQuery cQuery = new FluentQuery(); return cQuery.max(tableName, columnName, columnType); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor maxAsync(final String tableName, final String columnName, final Class columnType) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = max(tableName, columnName, columnType); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.min(Person.class, "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
     * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ public static T min(Class modelClass, String columnName, Class columnType) { return min(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor minAsync(final Class modelClass, final String columnName, final Class columnType) { return minAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.min("person", "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").min("person", "age", Integer.TYPE);
     * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ public static T min(String tableName, String columnName, Class columnType) { synchronized (LitePalSupport.class) { FluentQuery cQuery = new FluentQuery(); return cQuery.min(tableName, columnName, columnType); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor minAsync(final String tableName, final String columnName, final Class columnType) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = min(tableName, columnName, columnType); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.sum(Person.class, "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
     * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ public static T sum(Class modelClass, String columnName, Class columnType) { return sum(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor sumAsync(final Class modelClass, final String columnName, final Class columnType) { return sumAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); } /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.sum("person", "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").sum("person", "age", Integer.TYPE);
     * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ public static T sum(String tableName, String columnName, Class columnType) { synchronized (LitePalSupport.class) { FluentQuery cQuery = new FluentQuery(); return cQuery.sum(tableName, columnName, columnType); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor sumAsync(final String tableName, final String columnName, final Class columnType) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = sum(tableName, columnName, columnType); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Finds the record by a specific id. * *
     * Person p = LitePal.find(Person.class, 1);
     * 
* * The modelClass determines which table to query and the object type to * return. If no record can be found, then return null.
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link Operator#find(Class, long, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return An object with found data from database, or null. */ public static T find(Class modelClass, long id) { return find(modelClass, id, false); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor findAsync(Class modelClass, long id) { return findAsync(modelClass, id, false); } /** * It is mostly same as {@link Operator#find(Class, long)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ public static T find(Class modelClass, long id, boolean isEager) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onFind(modelClass, id, isEager); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor findAsync(final Class modelClass, final long id, final boolean isEager) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = find(modelClass, id, isEager); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Finds the first record of a single table. * *
     * Person p = LitePal.findFirst(Person.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link Operator#findFirst(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of first row, or null. */ public static T findFirst(Class modelClass) { return findFirst(modelClass, false); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor findFirstAsync(Class modelClass) { return findFirstAsync(modelClass, false); } /** * It is mostly same as {@link Operator#findFirst(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ public static T findFirst(Class modelClass, boolean isEager) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onFindFirst(modelClass, isEager); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor findFirstAsync(final Class modelClass, final boolean isEager) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = findFirst(modelClass, isEager); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Finds the last record of a single table. * *
     * Person p = LitePal.findLast(Person.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link Operator#findLast(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of last row, or null. */ public static T findLast(Class modelClass) { return findLast(modelClass, false); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor findLastAsync(Class modelClass) { return findLastAsync(modelClass, false); } /** * It is mostly same as {@link Operator#findLast(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ public static T findLast(Class modelClass, boolean isEager) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onFindLast(modelClass, isEager); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindExecutor findLastAsync(final Class modelClass, final boolean isEager) { final FindExecutor executor = new FindExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final T t = findLast(modelClass, isEager); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Finds multiple records by an id array. * *
     * List<Person> people = LitePal.findAll(Person.class, 1, 2, 3);
     *
     * long[] bookIds = { 10, 18 };
     * List<Book> books = LitePal.findAll(Book.class, bookIds);
     * 
* * Of course you can find all records by passing nothing to the ids * parameter. * *
     * List<Book> allBooks = LitePal.findAll(Book.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link Operator#findAll(Class, boolean, long...)}. * * The modelClass determines which table to query and the object type to * return. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ public static List findAll(Class modelClass, long... ids) { return findAll(modelClass, false, ids); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindMultiExecutor findAllAsync(Class modelClass, long... ids) { return findAllAsync(modelClass, false, ids); } /** * It is mostly same as {@link Operator#findAll(Class, long...)} but an * isEager parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ public static List findAll(Class modelClass, boolean isEager, long... ids) { synchronized (LitePalSupport.class) { QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); return queryHandler.onFindAll(modelClass, isEager, ids); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static FindMultiExecutor findAllAsync(final Class modelClass, final boolean isEager, final long... ids) { final FindMultiExecutor executor = new FindMultiExecutor<>(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final List t = findAll(modelClass, isEager, ids); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(t); } }); } } } }; executor.submit(runnable); return executor; } /** * Runs the provided SQL and returns a Cursor over the result set. You may * include ? in where clause in the query, which will be replaced by the * second to the last parameters, such as: * *
     * Cursor cursor = LitePal.findBySQL("select * from person where name=? and age=?", "Tom", "14");
     * 
* * @param sql * First parameter is the SQL clause to apply. Second to the last * parameters will replace the place holders. * @return A Cursor object, which is positioned before the first entry. Note * that Cursors are not synchronized, see the documentation for more * details. */ public static Cursor findBySQL(String... sql) { synchronized (LitePalSupport.class) { BaseUtility.checkConditionsCorrect(sql); if (sql == null) { return null; } if (sql.length <= 0) { return null; } String[] selectionArgs; if (sql.length == 1) { selectionArgs = null; } else { selectionArgs = new String[sql.length - 1]; System.arraycopy(sql, 1, selectionArgs, 0, sql.length - 1); } return Connector.getDatabase().rawQuery(sql[0], selectionArgs); } } /** * Deletes the record in the database by id.
* The data in other tables which is referenced with the record will be * removed too. * *
     * LitePal.delete(Person.class, 1);
     * 
* * This means that the record 1 in person table will be removed. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ public static int delete(Class modelClass, long id) { synchronized (LitePalSupport.class) { int rowsAffected; SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { DeleteHandler deleteHandler = new DeleteHandler(db); rowsAffected = deleteHandler.onDelete(modelClass, id); db.setTransactionSuccessful(); return rowsAffected; } finally { db.endTransaction(); } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static UpdateOrDeleteExecutor deleteAsync(final Class modelClass, final long id) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = delete(modelClass, id); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * *
     * LitePal.deleteAll(Person.class, "name = ? and age = ?", "Tom", "14");
     * 
* * This means that all the records which name is Tom and age is 14 will be * removed.
* * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int deleteAll(Class modelClass, String... conditions) { synchronized (LitePalSupport.class) { int rowsAffected; SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { DeleteHandler deleteHandler = new DeleteHandler(db); rowsAffected = deleteHandler.onDeleteAll(modelClass, conditions); db.setTransactionSuccessful(); return rowsAffected; } finally { db.endTransaction(); } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static UpdateOrDeleteExecutor deleteAllAsync(final Class modelClass, final String... conditions) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = deleteAll(modelClass, conditions); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * *
     * LitePal.deleteAll("person", "name = ? and age = ?", "Tom", "14");
     * 
* * This means that all the records which name is Tom and age is 14 will be * removed.
* * Note that this method won't delete the referenced data in other tables. * You should remove those values by your own. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int deleteAll(String tableName, String... conditions) { synchronized (LitePalSupport.class) { DeleteHandler deleteHandler = new DeleteHandler(Connector.getDatabase()); return deleteHandler.onDeleteAll(tableName, conditions); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static UpdateOrDeleteExecutor deleteAllAsync(final String tableName, final String... conditions) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = deleteAll(tableName, conditions); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Updates the corresponding record by id with ContentValues. Returns the * number of affected rows. * *
     * ContentValues cv = new ContentValues();
     * cv.put("name", "Jim");
     * LitePal.update(Person.class, cv, 1);
     * 
* * This means that the name of record 1 will be updated into Jim.
* * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return The number of rows affected. */ public static int update(Class modelClass, ContentValues values, long id) { synchronized (LitePalSupport.class) { UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); return updateHandler.onUpdate(modelClass, id, values); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static UpdateOrDeleteExecutor updateAsync(final Class modelClass, final ContentValues values, final long id) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = update(modelClass, values, id); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * *
     * ContentValues cv = new ContentValues();
     * cv.put("name", "Jim");
     * LitePal.update(Person.class, cv, "name = ?", "Tom");
     * 
* * This means that all the records which name is Tom will be updated into * Jim. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int updateAll(Class modelClass, ContentValues values, String... conditions) { return updateAll(BaseUtility.changeCase(DBUtility.getTableNameByClassName( modelClass.getName())), values, conditions); } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static UpdateOrDeleteExecutor updateAllAsync(Class modelClass, ContentValues values, String... conditions) { return updateAllAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName( modelClass.getName())), values, conditions); } /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * *
     * ContentValues cv = new ContentValues();
     * cv.put("name", "Jim");
     * LitePal.update("person", cv, "name = ?", "Tom");
     * 
* * This means that all the records which name is Tom will be updated into * Jim. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int updateAll(String tableName, ContentValues values, String... conditions) { synchronized (LitePalSupport.class) { UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); return updateHandler.onUpdateAll(tableName, values, conditions); } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static UpdateOrDeleteExecutor updateAllAsync(final String tableName, final ContentValues values, final String... conditions) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = updateAll(tableName, values, conditions); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Saves the collection into database.
* *
     * LitePal.saveAll(people);
     * 
* * If the model in collection is a new record gets created in the database, * otherwise the existing record gets updated.
* If saving process failed by any accident, the whole action will be * cancelled and your database will be rolled back.
* This method acts the same result as the below way, but much more * efficient. * *
     * for (Person person : people) {
     * 	person.save();
     * }
     * 
* * So when your collection holds huge of models, saveAll(Collection) is the better choice. * * @param collection * Holds all models to save. * @return True if all records in collection are saved. False none record in collection is saved. There won't be partial saved condition. */ public static boolean saveAll(Collection collection) { synchronized (LitePalSupport.class) { SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { SaveHandler saveHandler = new SaveHandler(db); saveHandler.onSaveAll(collection); db.setTransactionSuccessful(); return true; } catch (Exception e) { e.printStackTrace(); return false; } finally { db.endTransaction(); } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public static SaveExecutor saveAllAsync(final Collection collection) { final SaveExecutor executor = new SaveExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { boolean success; try { saveAll(collection); success = true; } catch (Exception e) { success = false; } final boolean result = success; if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(result); } }); } } } }; executor.submit(runnable); return executor; } /** * Provide a way to mark all models in collection as deleted. This means these models' save * state is no longer exist anymore. If save them again, they will be treated as inserting new * data instead of updating the exist one. * @param collection * Collection of models which want to mark as deleted and clear their save state. */ public static void markAsDeleted(Collection collection) { for (T t : collection) { t.clearSavedState(); } } /** * Check if the specified conditions data already exists in the table. * @param modelClass * Which table to check by class. * @param conditions * A filter declaring which data to check. Exactly same use as * {@link Operator#where(String...)}, except null conditions will result in false. * @return Return true if the specified conditions data already exists in the table. * False otherwise. Null conditions will result in false. */ public static boolean isExist(Class modelClass, String... conditions) { return conditions != null && where(conditions).count(modelClass) > 0; } /** * Register a listener to listen database create and upgrade events. */ public static void registerDatabaseListener(DatabaseListener listener) { dbListener = listener; } public static DatabaseListener getDBListener() { return dbListener; } } ================================================ FILE: core/src/main/java/org/litepal/annotation/Column.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used for adding constraints to a column. Note that this annotation won't affect id column. * * @author Tony Green * @since 1.3 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Column { /** * Set nullable constraint for the column. */ boolean nullable() default true; /** * Set unique constraint for the column. */ boolean unique() default false; /** * Set default value with String type for the column regardless of what column type is. */ String defaultValue() default ""; /** * Ignore to map this field into a column. */ boolean ignore() default false; /** * Add index for the column. */ boolean index() default false; } ================================================ FILE: core/src/main/java/org/litepal/annotation/Encrypt.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used for encrypt string field value when persisted into database. * * @author Tony Green * @since 1.6 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Encrypt { /** * Set the algorithm for encryption. */ String algorithm(); } ================================================ FILE: core/src/main/java/org/litepal/crud/AssociationsAnalyzer.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import org.litepal.LitePalBase; import org.litepal.crud.model.AssociationsInfo; import org.litepal.exceptions.LitePalSupportException; import org.litepal.util.DBUtility; /** * Base class of associations analyzer. * * @author Tony Green * @since 1.1 */ abstract class AssociationsAnalyzer extends DataHandler { /** * Get the associated models collection of associated model. Used for * reverse searching associations. * * @param associatedModel * The associated model of baseObj. * @param associationInfo * To get reverse associated models collection. * @return The associated models collection of associated model by analyzing * associationInfo. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ @SuppressWarnings("unchecked") protected Collection getReverseAssociatedModels(LitePalSupport associatedModel, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { return (Collection) getFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel()); } /** * Set the associated models collection of associated model. Break quote of * source collection. * * @param associatedModel * The associated model of baseObj. * @param associationInfo * To get reverse associated models collection. * @param associatedModelCollection * The new associated models collection with same data as source * collection but different quote. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ protected void setReverseAssociatedModels(LitePalSupport associatedModel, AssociationsInfo associationInfo, Collection associatedModelCollection) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { setFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel(), associatedModelCollection); } /** * Check the associated model collection. If the associated model collection * is null, try to initialize the associated model collection by the given * associated field. If the associated field is subclass of List, make an * instance of ArrayList for associated model collection. If the associated * field is subclass of Set, make an instance of HashSet for associated * model collection. If the associated model collection is not null, doing * nothing. * * @param associatedModelCollection * The associated model collection to check null and initialize. * @param associatedField * The field to decide which type to initialize for associated * model collection. * @throws LitePalSupportException */ protected Collection checkAssociatedModelCollection( Collection associatedModelCollection, Field associatedField) { Collection collection = null; if (isList(associatedField.getType())) { collection = new ArrayList(); } else if (isSet(associatedField.getType())) { collection = new HashSet(); } else { throw new LitePalSupportException(LitePalSupportException.WRONG_FIELD_TYPE_FOR_ASSOCIATIONS); } if (associatedModelCollection != null) { collection.addAll(associatedModelCollection); } return collection; } /** * Build the bidirectional association by setting the baseObj instance to * the associated model. * * @param baseObj * The instance of self model. * @param associatedModel * The associated model. * @param associationInfo * The association info to get the association. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ protected void buildBidirectionalAssociations(LitePalSupport baseObj, LitePalSupport associatedModel, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { setFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel(), baseObj); } /** * If the associated model is saved, add its' name and id to baseObj by * calling {@link LitePalSupport#addAssociatedModelWithFK(String, long)}. Or if * the baseObj is saved, add its' name and id to associated model by calling * {@link LitePalSupport#addAssociatedModelWithoutFK(String, long)}. * * @param baseObj * The baseObj currently want to persist. * @param associatedModel * The associated model. */ protected void dealsAssociationsOnTheSideWithoutFK(LitePalSupport baseObj, LitePalSupport associatedModel) { if (associatedModel != null) { if (associatedModel.isSaved()) { baseObj.addAssociatedModelWithFK(associatedModel.getTableName(), associatedModel.getBaseObjId()); } else { if (baseObj.isSaved()) { associatedModel.addAssociatedModelWithoutFK(baseObj.getTableName(), baseObj.getBaseObjId()); } } } } /** * If the associated model of self model is null, the FK value in database * should be cleared if it exists when updating. * * @param baseObj * The baseObj currently want to persist or update. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. */ protected void mightClearFKValue(LitePalSupport baseObj, AssociationsInfo associationInfo) { baseObj.addFKNameToClearSelf(getForeignKeyName(associationInfo)); } /** * Get foreign key name by {@link org.litepal.crud.model.AssociationsInfo}. * * @param associationInfo * To get foreign key name from. * @return The foreign key name. */ private String getForeignKeyName(AssociationsInfo associationInfo) { return getForeignKeyColumnName(DBUtility.getTableNameByClassName(associationInfo .getAssociatedClassName())); } } ================================================ FILE: core/src/main/java/org/litepal/crud/DataHandler.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import android.util.SparseArray; import org.litepal.LitePalBase; import org.litepal.Operator; import org.litepal.annotation.Column; import org.litepal.annotation.Encrypt; import org.litepal.crud.model.AssociationsInfo; import org.litepal.exceptions.DatabaseGenerateException; import org.litepal.exceptions.LitePalSupportException; import org.litepal.tablemanager.model.GenericModel; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.DBUtility; import org.litepal.util.cipher.CipherUtil; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import static org.litepal.util.BaseUtility.changeCase; /** * This is the base class for CRUD component. All the common actions which can * be shared with each function in CURD component will be put here. * * @author Tony Green * @since 1.1 */ abstract class DataHandler extends LitePalBase { public static final String TAG = "DataHandler"; /** * Instance of SQLiteDatabase, use to do the CRUD job. */ SQLiteDatabase mDatabase; /** * Store empty model instance. In case to create each time when checking * field is with default value or not. */ private LitePalSupport tempEmptyModel; /** * Holds the AssociationsInfo which foreign keys in the current model. */ private List fkInCurrentModel; /** * Holds the AssociationsInfo which foreign keys in other models. */ private List fkInOtherModel; /** * Query the table of the given model, returning a model list over the * result set. * * @param modelClass * The model to compile the query against. * @param columns * A list of which columns to return. Passing null will return * all columns, which is discouraged to prevent reading data from * storage that isn't going to be used. * @param selection * A filter declaring which rows to return, formatted as an SQL * WHERE clause (excluding the WHERE itself). Passing null will * return all rows for the given table. * @param selectionArgs * You may include ?s in selection, which will be replaced by the * values from selectionArgs, in order that they appear in the * selection. The values will be bound as Strings. * @param groupBy * A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. * @param having * A filter declare which row groups to include in the cursor, if * row grouping is being used, formatted as an SQL HAVING clause * (excluding the HAVING itself). Passing null will cause all row * groups to be included, and is required when row grouping is * not being used. * @param orderBy * How to order the rows, formatted as an SQL ORDER BY clause * (excluding the ORDER BY itself). Passing null will use the * default sort order, which may be unordered. * @param limit * Limits the number of rows returned by the query, formatted as * LIMIT clause. Passing null denotes no LIMIT clause. * @param foreignKeyAssociations * Associated classes which have foreign keys in the current * model's table. * @return A model list. The list may be empty. */ @SuppressWarnings("unchecked") protected List query(Class modelClass, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, List foreignKeyAssociations) { List dataList = new ArrayList<>(); Cursor cursor = null; try { List supportedFields = getSupportedFields(modelClass.getName()); List supportedGenericFields = getSupportedGenericFields(modelClass.getName()); String[] customizedColumns = DBUtility.convertSelectClauseToValidNames(getCustomizedColumns(columns, supportedGenericFields, foreignKeyAssociations)); String tableName = getTableName(modelClass); cursor = mDatabase.query(tableName, customizedColumns, selection, selectionArgs, groupBy, having, orderBy, limit); if (cursor.moveToFirst()) { SparseArray queryInfoCacheSparseArray = new SparseArray<>(); Map genericModelMap = new HashMap<>(); do { T modelInstance = (T) createInstanceFromClass(modelClass); giveBaseObjIdValue((LitePalSupport) modelInstance, cursor.getLong(cursor.getColumnIndexOrThrow("id"))); setValueToModel(modelInstance, supportedFields, foreignKeyAssociations, cursor, queryInfoCacheSparseArray); setGenericValueToModel((LitePalSupport) modelInstance, supportedGenericFields, genericModelMap); if (foreignKeyAssociations != null) { setAssociatedModel((LitePalSupport) modelInstance); } dataList.add(modelInstance); } while (cursor.moveToNext()); queryInfoCacheSparseArray.clear(); genericModelMap.clear(); } return dataList; } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } finally { if (cursor != null) { cursor.close(); } } } /** * Handles the math query of the given table. * * @param tableName * Which table to query from. * @param columns * A list of which columns to return. Passing null will return * all columns, which is discouraged to prevent reading data from * storage that isn't going to be used. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @param type * The type of the based on column. * @return The result calculating by SQL. */ @SuppressWarnings("unchecked") protected T mathQuery(String tableName, String[] columns, String[] conditions, Class type) { BaseUtility.checkConditionsCorrect(conditions); Cursor cursor = null; T result = null; try { cursor = mDatabase.query(tableName, columns, getWhereClause(conditions), getWhereArgs(conditions), null, null, null); if (cursor.moveToFirst()) { Class cursorClass = cursor.getClass(); Method method = cursorClass.getMethod(genGetColumnMethod(type), int.class); result = (T) method.invoke(cursor, 0); } } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } finally { if (cursor != null) { cursor.close(); } } return result; } /** * Assign the generated id value to {@link LitePalSupport#baseObjId}. This * value will be used as identify of this model for system use. * * @param baseObj * The class of base object. * @param id * The value of id. */ protected void giveBaseObjIdValue(LitePalSupport baseObj, long id) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { if (id > 0) { DynamicExecutor.set(baseObj, "baseObjId", id, LitePalSupport.class); } } /** * Iterate all the fields passed in. Each field calls * {@link #putFieldsValueDependsOnSaveOrUpdate(LitePalSupport, java.lang.reflect.Field, android.content.ContentValues)} * if it's not id field. * * @param baseObj * Current model to persist or update. * @param supportedFields * List of all supported fields. * @param values * To store data of current model for persisting or updating. */ protected void putFieldsValue(LitePalSupport baseObj, List supportedFields, ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { for (Field field : supportedFields) { if (!isIdColumn(field.getName())) { putFieldsValueDependsOnSaveOrUpdate(baseObj, field, values); } } } /** * This method deals with the putting values job into ContentValues. The * ContentValues has put method to set data. But we do not know we * should use which put method cause the field type isn't clear. So * the reflection API is necessary here to put values into ContentValues * with dynamically getting field type to put value. * * @param baseObj * The class of base object. * @param field * Field to put into ContentValues. * @param values * To store data of current model for persisting or updating. */ protected void putContentValuesForSave(LitePalSupport baseObj, Field field, ContentValues values) throws SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Object fieldValue = getFieldValue(baseObj, field); if ("java.util.Date".equals(field.getType().getName())) { // handle java.util.Date type for special if (fieldValue != null) { // If Date field is not null, use date.getTime() value for save. Date date = (Date) fieldValue; fieldValue = date.getTime(); } else { // If Date field is null, try to use defaultValue on annotation first. Column annotation = field.getAnnotation(Column.class); if (annotation != null) { String defaultValue = annotation.defaultValue(); if (!defaultValue.isEmpty()) { try { fieldValue = Long.parseLong(defaultValue); } catch (NumberFormatException e) { Log.w(TAG, field + " in " + baseObj.getClass() + " with invalid defaultValue. So we use null instead"); } } } if (fieldValue == null) { // If Date field is still null, use Long.MAX_VALUE for save. Because it's a date that will never reach. fieldValue = Long.MAX_VALUE; } } } if (fieldValue != null) { // put content value only when value is not null. this allows to use defaultValue declared in annotation. Encrypt annotation = field.getAnnotation(Encrypt.class); if (annotation != null && "java.lang.String".equals(field.getType().getName())) { fieldValue = encryptValue(annotation.algorithm(), fieldValue); } Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), fieldValue }; Class[] parameterTypes = getParameterTypes(field, fieldValue, parameters); DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); } } /** * putContentValuesForUpdate operation is almost same with putContentValuesForSave, except allowing put null fieldValue into database, * which is made for {@link LitePalSupport#setToDefault} function. * * @param baseObj * The class of base object. * @param field * Field to put into ContentValues. * @param values * To store data of current model for persisting or updating. */ protected void putContentValuesForUpdate(LitePalSupport baseObj, Field field, ContentValues values) throws SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Object fieldValue = getFieldValue(baseObj, field); if ("java.util.Date".equals(field.getType().getName())) { if (fieldValue != null) { Date date = (Date) fieldValue; fieldValue = date.getTime(); } else { // If Date field is null, use Long.MAX_VALUE for save. Because it's a date that will never reach. fieldValue = Long.MAX_VALUE; } } Encrypt annotation = field.getAnnotation(Encrypt.class); if (annotation != null && "java.lang.String".equals(field.getType().getName())) { fieldValue = encryptValue(annotation.algorithm(), fieldValue); } Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), fieldValue }; Class[] parameterTypes = getParameterTypes(field, fieldValue, parameters); DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); } /** * Encrypt the field value with targeted algorithm. * @param algorithm * The algorithm to encrypt value. * @param fieldValue * Field value to encrypt. * @return Encrypted value by targeted algorithm. */ protected Object encryptValue(String algorithm, Object fieldValue) { if (algorithm != null && fieldValue != null) { if (LitePalSupport.AES.equalsIgnoreCase(algorithm)) { fieldValue = CipherUtil.aesEncrypt((String) fieldValue); } else if (LitePalSupport.MD5.equalsIgnoreCase(algorithm)) { fieldValue = CipherUtil.md5Encrypt((String) fieldValue); } } return fieldValue; } /** * Get the field value for model. * * @param dataSupport * The model to get method from. * @param field * Use to generate getter method name. * @return The value returned by getter method. */ protected Object getFieldValue(LitePalSupport dataSupport, Field field) throws SecurityException, IllegalArgumentException, IllegalAccessException { if (shouldGetOrSet(dataSupport, field)) { return DynamicExecutor.getField(dataSupport, field.getName(), dataSupport.getClass()); } return null; } /** * Set the field value for model. * * @param dataSupport * The model to set method to. * @param field * Use to generate setter method name. * @param parameter * The parameter to invoke setter method. */ protected void setFieldValue(LitePalSupport dataSupport, Field field, Object parameter) throws SecurityException, IllegalArgumentException, IllegalAccessException { if (shouldGetOrSet(dataSupport, field)) { DynamicExecutor.setField(dataSupport, field.getName(), parameter, dataSupport.getClass()); } } /** * Find all the associated models of currently model. Then add all the * associated models into baseObj. * * @param baseObj * The class of base object. */ protected void analyzeAssociatedModels(LitePalSupport baseObj, Collection associationInfos) { try { for (AssociationsInfo associationInfo : associationInfos) { if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE) { new Many2OneAnalyzer().analyze(baseObj, associationInfo); } else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { new One2OneAnalyzer().analyze(baseObj, associationInfo); } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) { new Many2ManyAnalyzer().analyze(baseObj, associationInfo); } } } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Get the associated model. * * @param baseObj * The instance of self model. * @param associationInfo * To get the associated model. * @return The associated model of self model by analyzing associationInfo. */ protected LitePalSupport getAssociatedModel(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, IllegalAccessException { return (LitePalSupport) getFieldValue(baseObj, associationInfo.getAssociateOtherModelFromSelf()); } /** * Get the associated models collection. When it comes to many2one or * many2many association. A model may have lots of associated models. * * @param baseObj * The instance of self model. * @param associationInfo * To get the associated models collection. * @return The associated models collection of self model by analyzing * associationInfo. */ @SuppressWarnings("unchecked") protected Collection getAssociatedModels(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, IllegalAccessException { return (Collection) getFieldValue(baseObj, associationInfo.getAssociateOtherModelFromSelf()); } /** * Create an empty instance of baseObj if it hasn't created one yet. If * there's already an empty model existed in {@link #tempEmptyModel}, no * need to create a new one. * * @param baseObj * Current model to update. * @return An empty instance of baseObj. */ protected LitePalSupport getEmptyModel(LitePalSupport baseObj) { if (tempEmptyModel != null) { return tempEmptyModel; } String className = null; try { className = baseObj.getClassName(); Class modelClass = Class.forName(className); tempEmptyModel = (LitePalSupport) modelClass.newInstance(); return tempEmptyModel; } catch (ClassNotFoundException e) { throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); } catch (InstantiationException e) { throw new LitePalSupportException(className + LitePalSupportException.INSTANTIATION_EXCEPTION, e); } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Get the WHERE clause to apply when updating or deleting multiple rows. * * @param conditions * A string array representing the WHERE part of an SQL * statement. * @return The WHERE clause to apply when updating or deleting multiple * rows. */ protected String getWhereClause(String... conditions) { if (isAffectAllLines((Object) conditions)) { return null; } if (conditions != null && conditions.length > 0) { return conditions[0]; } return null; } /** * Get the WHERE arguments to fill into where clause when updating or * deleting multiple rows. * * @param conditions * A string array representing the WHERE part of an SQL * statement. * @return The WHERE arguments to fill into where clause when updating or * deleting multiple rows. */ protected String[] getWhereArgs(String... conditions) { if (isAffectAllLines((Object) conditions)) { return null; } if (conditions != null && conditions.length > 1) { String[] whereArgs = new String[conditions.length - 1]; System.arraycopy(conditions, 1, whereArgs, 0, conditions.length - 1); return whereArgs; } return null; } /** * Check the passing conditions represent to affect all lines or not.
* Do not pass anything to the conditions parameter means affect all lines. * * @param conditions * An array representing the WHERE part of an SQL statement. * @return Affect all lines or not. */ protected boolean isAffectAllLines(Object... conditions) { return conditions != null && conditions.length == 0; } /** * Get the where clause by the passed in id collection to apply multiple * rows. * * @param ids * The id collection. * @return The where clause to execute. */ protected String getWhereOfIdsWithOr(Collection ids) { StringBuilder whereClause = new StringBuilder(); boolean needOr = false; for (long id : ids) { if (needOr) { whereClause.append(" or "); } needOr = true; whereClause.append("id = "); whereClause.append(id); } return changeCase(whereClause.toString()); } /** * Get the where clause by the passed in id array to apply multiple rows. * * @param ids * The id collection. * @return The where clause to execute. */ protected String getWhereOfIdsWithOr(long... ids) { StringBuilder whereClause = new StringBuilder(); boolean needOr = false; for (long id : ids) { if (needOr) { whereClause.append(" or "); } needOr = true; whereClause.append("id = "); whereClause.append(id); } return changeCase(whereClause.toString()); } /** * When executing {@link #getFieldValue(LitePalSupport, Field)} or * {@link #setFieldValue(LitePalSupport, Field, Object)}, the * dataSupport and field passed in should be protected from null value. * * @param dataSupport * The object to execute set or get method. * @param field * The field of generating set and get methods. * @return True if dataSupport and field are not null, false otherwise. */ protected boolean shouldGetOrSet(LitePalSupport dataSupport, Field field) { return dataSupport != null && field != null; } /** * Get the name of intermediate join table. * * @param baseObj * Current model. * @param associatedTableName * The name of associated table. * @return The name of intermediate join table. */ protected String getIntermediateTableName(LitePalSupport baseObj, String associatedTableName) { return changeCase(DBUtility.getIntermediateTableName(baseObj.getTableName(), associatedTableName)); } /** * Get the simple name of modelClass. Then change the case by the setting * rule in litepal.xml as table name. * * @param modelClass * Class of model to get table name from. * @return The table name of model. */ protected String getTableName(Class modelClass) { return BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())); } /** * Creates an instance from the passed in class. It will always create an * instance no matter how the constructor defines in the class file. A best * suit constructor will be find by calling * {@link #findBestSuitConstructor(Class)} method. * * @param modelClass * The class to create instance. * @return An instance by the passed in class. */ protected Object createInstanceFromClass(Class modelClass) { try { Constructor constructor = findBestSuitConstructor(modelClass); return constructor.newInstance(getConstructorParams(modelClass, constructor)); } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Finds the best suit constructor for creating an instance of a class. The * principle is that the constructor with least parameters and has no self * type parameter will be the best suit one to create instance. * * @param modelClass * To get constructors from. * @return The best suit constructor. */ protected Constructor findBestSuitConstructor(Class modelClass) { Constructor[] constructors = modelClass.getDeclaredConstructors(); if (constructors.length == 0) throw new LitePalSupportException( modelClass.getName() + " has no constructor. LitePal could not handle it"); Constructor bestSuitConstructor = null; int minConstructorParamLength = Integer.MAX_VALUE; for (Constructor constructor : constructors) { Class[] types = constructor.getParameterTypes(); boolean canUseThisConstructor = true; // under some conditions, constructor can not use for create instance for (Class parameterType : types) { if (parameterType == modelClass || parameterType.getName().startsWith("com.android") && parameterType.getName().endsWith("InstantReloadException")) { // we can not use this constructor canUseThisConstructor = false; break; } } if (canUseThisConstructor) { // we can use this constructor if (types.length < minConstructorParamLength) { // find the constructor with least parameter bestSuitConstructor = constructor; minConstructorParamLength = types.length; } } } if (bestSuitConstructor != null) { bestSuitConstructor.setAccessible(true); } else { StringBuilder builder = new StringBuilder(modelClass.getName()).append(" has no suited constructor to new instance. Constructors defined in class:"); for (Constructor constructor : constructors) { builder.append("\n").append(constructor.toString()); } throw new LitePalSupportException(builder.toString()); } return bestSuitConstructor; } /** * Depends on the passed in constructor, creating a parameters array with * initialized values for the constructor. * * @param modelClass * The original class the this constructor belongs to. * @param constructor * The constructor to get parameters for it. * * @return A parameters array with initialized values. */ protected Object[] getConstructorParams(Class modelClass, Constructor constructor) { Class[] paramTypes = constructor.getParameterTypes(); Object[] params = new Object[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { params[i] = getInitParamValue(modelClass, paramTypes[i]); } return params; } /** * Get value from database by cursor, then set the value into modelInstance. * * @param modelInstance * The model to set into. * @param supportedFields * Corresponding to each column in database. * @param foreignKeyAssociations * Associated classes which have foreign keys in the current * model's table. * @param cursor * Use to get value from database. * @param sparseArray * Use SparseArray to cache the query information at first loop. Then the rest loop * can get query information directly to speed up. */ protected void setValueToModel(Object modelInstance, List supportedFields, List foreignKeyAssociations, Cursor cursor, SparseArray sparseArray) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { int cacheSize = sparseArray.size(); if (cacheSize > 0) { for (int i = 0; i < cacheSize; i++) { int columnIndex = sparseArray.keyAt(i); QueryInfoCache cache = sparseArray.get(columnIndex); setToModelByReflection(modelInstance, cache.field, columnIndex, cache.getMethodName, cursor); } } else { for (Field field : supportedFields) { String getMethodName = genGetColumnMethod(field); String columnName = isIdColumn(field.getName()) ? "id" : DBUtility.convertToValidColumnName(field.getName()); int columnIndex = cursor.getColumnIndex(BaseUtility.changeCase(columnName)); if (columnIndex != -1) { setToModelByReflection(modelInstance, field, columnIndex, getMethodName, cursor); QueryInfoCache cache = new QueryInfoCache(); cache.getMethodName = getMethodName; cache.field = field; sparseArray.put(columnIndex, cache); } } } if (foreignKeyAssociations != null) { for (AssociationsInfo associationInfo : foreignKeyAssociations) { String foreignKeyColumn = getForeignKeyColumnName(DBUtility .getTableNameByClassName(associationInfo.getAssociatedClassName())); int columnIndex = cursor.getColumnIndex(foreignKeyColumn); if (columnIndex != -1) { long associatedClassId = cursor.getLong(columnIndex); try { LitePalSupport associatedObj = (LitePalSupport) Operator.find( Class.forName(associationInfo.getAssociatedClassName()), associatedClassId); if (associatedObj != null) { setFieldValue((LitePalSupport) modelInstance, associationInfo.getAssociateOtherModelFromSelf(), associatedObj); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } /** * Get generic value from generic tables, then set the value into the baseObj. * @param baseObj * The model to set into. * @param supportedGenericFields * List of all supported generic fields. * @param genericModelMap * Use HashMap to cache the query information at first loop. Then the rest loop can * get query information directly to speed up. */ protected void setGenericValueToModel(LitePalSupport baseObj, List supportedGenericFields, Map genericModelMap) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { for (Field field : supportedGenericFields) { String tableName, genericValueColumnName, genericValueIdColumnName, getMethodName; Cursor cursor = null; GenericModel genericModel = genericModelMap.get(field); if (genericModel == null) { String genericTypeName = getGenericTypeName(field); if (baseObj.getClassName().equals(genericTypeName)) { genericValueColumnName = DBUtility.getM2MSelfRefColumnName(field); getMethodName = "getLong"; } else { genericValueColumnName = DBUtility.convertToValidColumnName(field.getName()); getMethodName = genGetColumnMethod(field); } tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); GenericModel model = new GenericModel(); model.setTableName(tableName); model.setValueColumnName(genericValueColumnName); model.setValueIdColumnName(genericValueIdColumnName); model.setGetMethodName(getMethodName); genericModelMap.put(field, model); } else { tableName = genericModel.getTableName(); genericValueColumnName = genericModel.getValueColumnName(); genericValueIdColumnName = genericModel.getValueIdColumnName(); getMethodName = genericModel.getGetMethodName(); } try { cursor = mDatabase.query(tableName, null, genericValueIdColumnName + " = ?", new String[]{ String.valueOf(baseObj.getBaseObjId()) }, null, null, null); if (cursor.moveToFirst()) { do { int columnIndex = cursor.getColumnIndex(BaseUtility.changeCase(genericValueColumnName)); if (columnIndex != -1) { setToModelByReflection(baseObj, field, columnIndex, getMethodName, cursor); } } while (cursor.moveToNext()); } } finally { if (cursor != null) { cursor.close(); } } } } /** * Get the foreign key associations of the specified class. * * @param className * The full class name. * @param isEager * True to load the associated models, false not. * @return The foreign key associations of the specified class */ protected List getForeignKeyAssociations(String className, boolean isEager) { if (isEager) { analyzeAssociations(className); return fkInCurrentModel; } return null; } /** * Get the types of parameters for {@link android.content.ContentValues#put}. Need two * parameters. First is String type for key. Second is depend on field for * value. * * @param field * The field to get parameter type. * @param fieldValue * Value of the field. Only used to convert to String when the * field is char. * @param parameters * If the field is char, convert the value to String at index 1. * @return The types of parameters for {@link android.content.ContentValues#put}. */ protected Class[] getParameterTypes(Field field, Object fieldValue, Object[] parameters) { Class[] parameterTypes; if (isCharType(field)) { parameters[1] = String.valueOf(fieldValue); parameterTypes = new Class[] { String.class, String.class }; } else { if (field.getType().isPrimitive()) { parameterTypes = new Class[] { String.class, getObjectType(field.getType()) }; } else if ("java.util.Date".equals(field.getType().getName())) { parameterTypes = new Class[] { String.class, Long.class }; } else { parameterTypes = new Class[] { String.class, field.getType() }; } } return parameterTypes; } /** * Each primitive type has a corresponding object type. For example int and * Integer, boolean and Boolean. This method gives a way to turn primitive * type into object type. * * @param primitiveType * The class of primitive type. * @return If the passed in parameter is primitive type, return a * corresponding object type. Otherwise return null. */ private Class getObjectType(Class primitiveType) { if (primitiveType != null) { if (primitiveType.isPrimitive()) { String basicTypeName = primitiveType.getName(); switch (basicTypeName) { case "int": return Integer.class; case "short": return Short.class; case "long": return Long.class; case "float": return Float.class; case "double": return Double.class; case "boolean": return Boolean.class; case "char": return Character.class; } } } return null; } /** * Gives the passed in parameter an initialized value. If the parameter is * basic data type or the corresponding object data type, return the default * data. Or return null. * * @param modelClass * The original class the this constructor belongs to. * @param paramType * Parameter to get initialized value. * @return Default data of basic data type or null. */ private Object getInitParamValue(Class modelClass, Class paramType) { String paramTypeName = paramType.getName(); if ("boolean".equals(paramTypeName) || "java.lang.Boolean".equals(paramTypeName)) { return false; } if ("float".equals(paramTypeName) || "java.lang.Float".equals(paramTypeName)) { return 0f; } if ("double".equals(paramTypeName) || "java.lang.Double".equals(paramTypeName)) { return 0.0; } if ("int".equals(paramTypeName) || "java.lang.Integer".equals(paramTypeName)) { return 0; } if ("long".equals(paramTypeName) || "java.lang.Long".equals(paramTypeName)) { return 0L; } if ("short".equals(paramTypeName) || "java.lang.Short".equals(paramTypeName)) { return 0; } if ("char".equals(paramTypeName) || "java.lang.Character".equals(paramTypeName)) { return ' '; } if ("[B".equals(paramTypeName) || "[Ljava.lang.Byte;".equals(paramTypeName)) { return new byte[0]; } if ("java.lang.String".equals(paramTypeName)) { return ""; } if (modelClass == paramType) { return null; } return createInstanceFromClass(paramType); } /** * Judge if the field is char or Character type. * * @param field * Field to judge type. * @return Return true if it's char or Character. Otherwise return false. */ private boolean isCharType(Field field) { String type = field.getType().getName(); return type.equals("char") || type.endsWith("Character"); } /** * Judge a field is a primitive boolean type or not. Cause it's a little * special when use IDE to generate getter and setter method. The primitive * boolean type won't be like getXxx, it's something like * isXxx. * * @param field * Use field to get field type. * @return If it's primitive boolean type return true, else return false. */ private boolean isPrimitiveBooleanType(Field field) { Class fieldType = field.getType(); return "boolean".equals(fieldType.getName()); } /** * Put the value of field into ContentValues if current action is saving. * Check the value of field is default value or not if current action is * updating. If it's not default value, put it into ContentValues. Otherwise * ignore it. * * @param baseObj * Current model to persist or update. * @param field * With value to put into ContentValues. * @param values * To store data of current model for persisting or updating. */ private void putFieldsValueDependsOnSaveOrUpdate(LitePalSupport baseObj, Field field, ContentValues values) throws SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { if (isUpdating()) { if (!isFieldWithDefaultValue(baseObj, field)) { putContentValuesForUpdate(baseObj, field, values); } } else if (isSaving()) { putContentValuesForSave(baseObj, field, values); } } /** * Current action is updating or not. Note that update the record by saving * the already saved record again belongs to save action. * * @return If current action is updating return true. Otherwise return * false. */ private boolean isUpdating() { return UpdateHandler.class.getName().equals(getClass().getName()); } /** * Current action is saving or not. Note that update the record by saving * the already saved record again belongs to save action. * * @return If current action is saving return true. Otherwise return false. */ private boolean isSaving() { return SaveHandler.class.getName().equals(getClass().getName()); } /** * Analyze the passed in field. Check if this field is with default value. * The baseObj need a default constructor or {@link LitePalSupportException} * will be thrown. * * @param baseObj * Current model to update. * @param field * To check if with default value. * @return If the field is with default value, return true. Otherwise return * false. */ private boolean isFieldWithDefaultValue(LitePalSupport baseObj, Field field) throws IllegalAccessException, SecurityException, IllegalArgumentException { LitePalSupport emptyModel = getEmptyModel(baseObj); Object realReturn = getFieldValue(baseObj, field); Object defaultReturn = getFieldValue(emptyModel, field); if (realReturn != null && defaultReturn != null) { String realFieldValue = realReturn.toString(); String defaultFieldValue = defaultReturn.toString(); return realFieldValue.equals(defaultFieldValue); } return realReturn == defaultReturn; } /** * Generate the getter method name by field, following the Android Studio rule. * * @param field * The field to generate getter method from. * @return The generated getter method name. */ protected String makeGetterMethodName(Field field) { String getterMethodPrefix; String fieldName = field.getName(); if (isPrimitiveBooleanType(field)) { if (fieldName.matches("^is[A-Z]{1}.*$")) { fieldName = fieldName.substring(2); } getterMethodPrefix = "is"; } else { getterMethodPrefix = "get"; } if (fieldName.matches("^[a-z]{1}[A-Z]{1}.*")) { return getterMethodPrefix + fieldName; } else { return getterMethodPrefix + BaseUtility.capitalize(fieldName); } } /** * Generate the setter method name by field, following the Android Studio rule. * * @param field * The field to generate setter method from. * @return The generated setter method name. */ protected String makeSetterMethodName(Field field) { String setterMethodName; String setterMethodPrefix = "set"; if (isPrimitiveBooleanType(field) && field.getName().matches("^is[A-Z]{1}.*$")) { setterMethodName = setterMethodPrefix + field.getName().substring(2); } else if (field.getName().matches("^[a-z]{1}[A-Z]{1}.*")) { setterMethodName = setterMethodPrefix + field.getName(); } else { setterMethodName = setterMethodPrefix + BaseUtility.capitalize(field.getName()); } return setterMethodName; } /** * Generates the getType method for cursor based on field. There're couple of * unusual conditions. If field type is boolean, generate getInt method. If * field type is char, generate getString method. If field type is Date, generate * getLong method. If filed type is Integer, generate getInt method. If field type * is bytes, generate getBlob method. * * @param field * To generate getType method for cursor. * @return The getType method for cursor. */ private String genGetColumnMethod(Field field) { Class fieldType; if (isCollection(field.getType())) { fieldType = getGenericTypeClass(field); } else { fieldType = field.getType(); } return genGetColumnMethod(fieldType); } /** * Generates the getType method for cursor based on field. There're couple of * unusual conditions. If field type is boolean, generate getInt method. If * field type is char, generate getString method. If field type is Date, generate * getLong method. If filed type is Integer, generate getInt method. If field type * is bytes, generate getBlob method. * * @param fieldType * To generate getType method for cursor. * @return The getType method for cursor. */ private String genGetColumnMethod(Class fieldType) { String typeName; if (fieldType.isPrimitive()) { typeName = BaseUtility.capitalize(fieldType.getName()); } else { typeName = fieldType.getSimpleName(); } String methodName = "get" + typeName; switch (methodName) { case "getBoolean": case "getInteger": methodName = "getInt"; break; case "getChar": case "getCharacter": methodName = "getString"; break; case "getDate": methodName = "getLong"; break; } return methodName; } /** * Customize the passed in columns. If the columns contains an id column * already, just return it. If contains an _id column, rename it to id. If * not, an add id column then return. If it contains generic columns them * from query and use them in supported generic fields. * * @param columns * The original columns that passed in. * @param foreignKeyAssociations * Associated classes which have foreign keys in the current * model's table. * @return Customized columns with id column always. */ private String[] getCustomizedColumns(String[] columns, List supportedGenericFields, List foreignKeyAssociations) { if (columns != null && columns.length > 0) { boolean columnsContainsId = false; List convertList = Arrays.asList(columns); List columnList = new ArrayList<>(convertList); List supportedGenericFieldNames = new ArrayList<>(); List columnToRemove = new ArrayList<>(); List genericColumnsForQuery = new ArrayList<>(); List tempSupportedGenericFields = new ArrayList<>(); for (Field supportedGenericField : supportedGenericFields) { supportedGenericFieldNames.add(supportedGenericField.getName()); } for (int i = 0; i < columnList.size(); i++) { String columnName = columnList.get(i); // find out all generic columns. if (BaseUtility.containsIgnoreCases(supportedGenericFieldNames, columnName)) { columnToRemove.add(i); } else if (isIdColumn(columnName)) { columnsContainsId = true; if ("_id".equalsIgnoreCase(columnName)) { columnList.set(i, BaseUtility.changeCase("id")); } } } // remove generic columns cause they can't be used for query for (int i = columnToRemove.size() - 1; i >= 0 ; i--) { int index = columnToRemove.get(i); String genericColumn = columnList.remove(index); genericColumnsForQuery.add(genericColumn); } for (Field supportedGenericField : supportedGenericFields) { String fieldName = supportedGenericField.getName(); if (BaseUtility.containsIgnoreCases(genericColumnsForQuery, fieldName)) { tempSupportedGenericFields.add(supportedGenericField); } } supportedGenericFields.clear(); supportedGenericFields.addAll(tempSupportedGenericFields); if (foreignKeyAssociations != null && foreignKeyAssociations.size() > 0) { for (int i = 0; i < foreignKeyAssociations.size(); i++) { String associatedTable = DBUtility .getTableNameByClassName(foreignKeyAssociations.get(i) .getAssociatedClassName()); columnList.add(getForeignKeyColumnName(associatedTable)); } } if (!columnsContainsId) { columnList.add(BaseUtility.changeCase("id")); } return columnList.toArray(new String[0]); } return null; } /** * Analyze the associations for the specified class. * * @param className * The full class name. */ private void analyzeAssociations(String className) { Collection associationInfos = getAssociationInfo(className); if (fkInCurrentModel == null) { fkInCurrentModel = new ArrayList<>(); } else { fkInCurrentModel.clear(); } if (fkInOtherModel == null) { fkInOtherModel = new ArrayList<>(); } else { fkInOtherModel.clear(); } for (AssociationsInfo associationInfo : associationInfos) { if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE || associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { if (associationInfo.getClassHoldsForeignKey().equals(className)) { fkInCurrentModel.add(associationInfo); } else { fkInOtherModel.add(associationInfo); } } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) { fkInOtherModel.add(associationInfo); } } } /** * Finds the associated models of baseObj, then set them into baseObj. * * @param baseObj * The class of base object. */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void setAssociatedModel(LitePalSupport baseObj) { if (fkInOtherModel == null) { return; } for (AssociationsInfo info : fkInOtherModel) { Cursor cursor = null; String associatedClassName = info.getAssociatedClassName(); boolean isM2M = info.getAssociationType() == Const.Model.MANY_TO_MANY; try { List supportedFields = getSupportedFields(associatedClassName); List supportedGenericFields = getSupportedGenericFields(associatedClassName); if (isM2M) { String tableName = baseObj.getTableName(); String associatedTableName = DBUtility .getTableNameByClassName(associatedClassName); String intermediateTableName = DBUtility.getIntermediateTableName(tableName, associatedTableName); StringBuilder sql = new StringBuilder(); sql.append("select * from ").append(associatedTableName) .append(" a inner join ").append(intermediateTableName) .append(" b on a.id = b.").append(associatedTableName).append("_id") .append(" where b.").append(tableName).append("_id = ?"); cursor = Operator.findBySQL(BaseUtility.changeCase(sql.toString()), String.valueOf(baseObj.getBaseObjId())); } else { String foreignKeyColumn = getForeignKeyColumnName(DBUtility .getTableNameByClassName(info.getSelfClassName())); String associatedTableName = DBUtility .getTableNameByClassName(associatedClassName); cursor = mDatabase.query(BaseUtility.changeCase(associatedTableName), null, foreignKeyColumn + "=?", new String[] { String.valueOf(baseObj.getBaseObjId()) }, null, null, null, null); } if (cursor != null && cursor.moveToFirst()) { SparseArray queryInfoCacheSparseArray = new SparseArray<>(); Map genericModelMap = new HashMap<>(); do { LitePalSupport modelInstance = (LitePalSupport) createInstanceFromClass(Class.forName(associatedClassName)); giveBaseObjIdValue(modelInstance, cursor.getLong(cursor.getColumnIndexOrThrow("id"))); setValueToModel(modelInstance, supportedFields, null, cursor, queryInfoCacheSparseArray); setGenericValueToModel(modelInstance, supportedGenericFields, genericModelMap); if (info.getAssociationType() == Const.Model.MANY_TO_ONE || isM2M) { Field field = info.getAssociateOtherModelFromSelf(); Collection collection = (Collection) getFieldValue(baseObj, field); if (collection == null) { if (isList(field.getType())) { collection = new ArrayList(); } else { collection = new HashSet(); } DynamicExecutor.setField(baseObj, field.getName(), collection, baseObj.getClass()); } collection.add(modelInstance); } else if (info.getAssociationType() == Const.Model.ONE_TO_ONE) { setFieldValue(baseObj, info.getAssociateOtherModelFromSelf(), modelInstance); } } while (cursor.moveToNext()); queryInfoCacheSparseArray.clear(); genericModelMap.clear(); } } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } finally { if (cursor != null) { cursor.close(); } } } } @SuppressWarnings("unchecked") private void setToModelByReflection(Object modelInstance, Field field, int columnIndex, String getMethodName, Cursor cursor) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class cursorClass = cursor.getClass(); if (cursor.isNull(columnIndex)) return; Method method = cursorClass.getMethod(getMethodName, int.class); Object value = method.invoke(cursor, columnIndex); if (field.getType() == boolean.class || field.getType() == Boolean.class) { if ("0".equals(String.valueOf(value))) { value = false; } else if ("1".equals(String.valueOf(value))) { value = true; } } else if (field.getType() == char.class || field.getType() == Character.class) { value = ((String) value).charAt(0); } else if (field.getType() == Date.class) { long date = (long) value; if (date == Long.MAX_VALUE) { // Long.MAX_VALUE is a date that will never reach, which represents null in our case. value = null; } else { value = new Date(date); } } if (isCollection(field.getType())) { Collection collection = (Collection) DynamicExecutor.getField(modelInstance, field.getName(), modelInstance.getClass()); if (collection == null) { if (isList(field.getType())) { collection = new ArrayList<>(); } else { collection = new HashSet<>(); } DynamicExecutor.setField(modelInstance, field.getName(), collection, modelInstance.getClass()); } String genericTypeName = getGenericTypeName(field); if ("java.lang.String".equals(genericTypeName)) { Encrypt annotation = field.getAnnotation(Encrypt.class); if (annotation != null) { value = decryptValue(annotation.algorithm(), value); } } else if (modelInstance.getClass().getName().equals(genericTypeName)) { if (value instanceof Long || value instanceof Integer) { value = Operator.find(modelInstance.getClass(), (long) value); } } collection.add(value); } else { Encrypt annotation = field.getAnnotation(Encrypt.class); if (annotation != null && "java.lang.String".equals(field.getType().getName())) { value = decryptValue(annotation.algorithm(), value); } DynamicExecutor.setField(modelInstance, field.getName(), value, modelInstance.getClass()); } } /** * Decrypt the field value with targeted algorithm. * @param algorithm * The algorithm to decrypt value. * @param fieldValue * Field value to decrypt. * @return Decrypted value by targeted algorithm. */ protected Object decryptValue(String algorithm, Object fieldValue) { if (algorithm != null && fieldValue != null) { if (LitePalSupport.AES.equalsIgnoreCase(algorithm)) { fieldValue = CipherUtil.aesDecrypt((String) fieldValue); } } return fieldValue; } /** * Cache core info for query operation to improve query performance. * * @since 1.3.1 */ static class QueryInfoCache { String getMethodName; Field field; } } ================================================ FILE: core/src/main/java/org/litepal/crud/DeleteHandler.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import org.litepal.Operator; import org.litepal.crud.model.AssociationsInfo; import org.litepal.exceptions.LitePalSupportException; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.DBUtility; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; /** * This is a component under LitePalSupport. It deals with the deleting stuff as * primary task. If deletes a saved model or delete a record with id, the * cascade delete function would work. But considering efficiency, if deletes * with deleteAll method, the referenced data in other tables will not be * affected. Developers should remove those referenced data by their own. * * @author Tony Green * @since 1.1 */ public class DeleteHandler extends DataHandler { /** * To store associated tables of current model's table. Only used while * deleting by id and deleting all by model class. */ private List foreignKeyTableToDelete; /** * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not * allow to create instance of DeleteHandler out of CRUD package. * * @param db * The instance of SQLiteDatabase. */ public DeleteHandler(SQLiteDatabase db) { mDatabase = db; } /** * The open interface for other classes in CRUD package to delete. Using * baseObj to decide which record to delete. The baseObj must be saved * already(using {@link LitePalSupport#isSaved()} to test), or nothing will be * deleted. This method can action cascade delete. When the record is * deleted from database, all the referenced data such as foreign key value * will be removed too. * * @param baseObj * The record to delete. * @return The number of rows affected. Including cascade delete rows. */ int onDelete(LitePalSupport baseObj) { if (baseObj.isSaved()) { List supportedGenericFields = getSupportedGenericFields(baseObj.getClassName()); deleteGenericData(baseObj.getClass(), supportedGenericFields, baseObj.getBaseObjId()); Collection associationInfos = analyzeAssociations(baseObj); int rowsAffected = deleteCascade(baseObj); rowsAffected += mDatabase.delete(baseObj.getTableName(), "id = " + baseObj.getBaseObjId(), null); clearAssociatedModelSaveState(baseObj, associationInfos); return rowsAffected; } return 0; } /** * The open interface for other classes in CRUD package to delete. Using * modelClass to decide which table to delete from, and id to decide a * specific row. This method can action cascade delete. When the record is * deleted from database, all the referenced data such as foreign key value * will be removed too. * * @param modelClass * Which table to delete from. * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ public int onDelete(Class modelClass, long id) { List supportedGenericFields = getSupportedGenericFields(modelClass.getName()); deleteGenericData(modelClass, supportedGenericFields, id); analyzeAssociations(modelClass); int rowsAffected = deleteCascade(modelClass, id); rowsAffected += mDatabase.delete(getTableName(modelClass), "id = " + id, null); getForeignKeyTableToDelete().clear(); return rowsAffected; } /** * The open interface for other classes in CRUD package to delete multiple * rows. Using tableName to decide which table to delete from, and * conditions representing the WHERE part of an SQL statement. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. * @return The number of rows affected. */ public int onDeleteAll(String tableName, String... conditions) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } return mDatabase.delete(tableName, getWhereClause(conditions), getWhereArgs(conditions)); } @SuppressWarnings("unchecked") public int onDeleteAll(Class modelClass, String... conditions) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } List supportedGenericFields = getSupportedGenericFields(modelClass.getName()); if (!supportedGenericFields.isEmpty()) { List list = (List) Operator.select("id").where(conditions).find(modelClass); if (list.size() > 0) { long[] ids = new long[list.size()]; for (int i = 0; i < ids.length; i++) { LitePalSupport dataSupport = list.get(i); ids[i] = dataSupport.getBaseObjId(); } deleteGenericData(modelClass, supportedGenericFields, ids); } } analyzeAssociations(modelClass); int rowsAffected = deleteAllCascade(modelClass, conditions); rowsAffected += mDatabase.delete(getTableName(modelClass), getWhereClause(conditions), getWhereArgs(conditions)); getForeignKeyTableToDelete().clear(); return rowsAffected; } /** * Analyze the associations of modelClass and store the associated tables. * The associated tables might be used when deleting referenced data of a * specified row. * * @param modelClass * To get associations of this class. */ private void analyzeAssociations(Class modelClass) { Collection associationInfos = getAssociationInfo(modelClass .getName()); for (AssociationsInfo associationInfo : associationInfos) { String associatedTableName = DBUtility .getTableNameByClassName(associationInfo .getAssociatedClassName()); if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE || associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { String classHoldsForeignKey = associationInfo .getClassHoldsForeignKey(); if (!modelClass.getName().equals(classHoldsForeignKey)) { getForeignKeyTableToDelete().add(associatedTableName); } } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) { String joinTableName = DBUtility.getIntermediateTableName( getTableName(modelClass), associatedTableName); joinTableName = BaseUtility.changeCase(joinTableName); getForeignKeyTableToDelete().add(joinTableName); } } } /** * Use the analyzed result of associations to delete referenced data. So * this method must be called after {@link #analyzeAssociations(Class)}. * There're two parts of referenced data to delete. The foreign key rows in * associated table and the foreign key rows in intermediate join table. * * @param modelClass * To get the table name and combine with id as a foreign key * column. * @param id * Delete all the rows which referenced with this id. * @return The number of rows affected in associated tables and intermediate * join tables. */ private int deleteCascade(Class modelClass, long id) { int rowsAffected = 0; for (String associatedTableName : getForeignKeyTableToDelete()) { String fkName = getForeignKeyColumnName(getTableName(modelClass)); rowsAffected += mDatabase.delete(associatedTableName, fkName + " = " + id, null); } return rowsAffected; } private int deleteAllCascade(Class modelClass, String... conditions) { int rowsAffected = 0; for (String associatedTableName : getForeignKeyTableToDelete()) { String tableName = getTableName(modelClass); String fkName = getForeignKeyColumnName(tableName); StringBuilder whereClause = new StringBuilder(); whereClause.append(fkName).append(" in (select id from "); whereClause.append(tableName); if (conditions != null && conditions.length > 0) { whereClause.append(" where ").append(buildConditionString(conditions)); } whereClause.append(")"); rowsAffected += mDatabase.delete(associatedTableName, BaseUtility.changeCase(whereClause.toString()), null); } return rowsAffected; } private String buildConditionString(String... conditions) { int argCount = conditions.length - 1; String whereClause = conditions[0]; for (int i = 0; i < argCount; i++) { whereClause = whereClause.replaceFirst("\\?", "'" + conditions[i+1] + "'"); } return whereClause; } /** * Analyze the associations of baseObj and store the result in it. The * associations will be used when deleting referenced data of baseObj. * * @param baseObj * The record to delete. */ private Collection analyzeAssociations(LitePalSupport baseObj) { try { Collection associationInfos = getAssociationInfo(baseObj .getClassName()); analyzeAssociatedModels(baseObj, associationInfos); return associationInfos; } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Clear associated models' save state. After this method, the associated * models of baseObj which data is removed from database will become * unsaved. * * @param baseObj * The record to delete. * @param associationInfos * The associated info. */ private void clearAssociatedModelSaveState(LitePalSupport baseObj, Collection associationInfos) { try { for (AssociationsInfo associationInfo : associationInfos) { if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE && !baseObj.getClassName().equals( associationInfo.getClassHoldsForeignKey())) { Collection associatedModels = getAssociatedModels( baseObj, associationInfo); if (associatedModels != null && !associatedModels.isEmpty()) { for (LitePalSupport model : associatedModels) { if (model != null) { model.clearSavedState(); } } } } else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { LitePalSupport model = getAssociatedModel(baseObj, associationInfo); if (model != null) { model.clearSavedState(); } } } } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Use the analyzed result of associations to delete referenced data. So * this method must be called after * {@link #analyzeAssociations(LitePalSupport)}. There're two parts of * referenced data to delete. The foreign key rows in associated tables and * the foreign key rows in intermediate join tables. * * @param baseObj * The record to delete. Now contains associations info. * @return The number of rows affected in associated table and intermediate * join table. */ private int deleteCascade(LitePalSupport baseObj) { int rowsAffected; rowsAffected = deleteAssociatedForeignKeyRows(baseObj); rowsAffected += deleteAssociatedJoinTableRows(baseObj); return rowsAffected; } /** * Delete the referenced rows of baseObj in associated tables(Many2One and * One2One conditions). * * @param baseObj * The record to delete. Now contains associations info. * @return The number of rows affected in all associated tables. */ private int deleteAssociatedForeignKeyRows(LitePalSupport baseObj) { int rowsAffected = 0; Map> associatedModelMap = baseObj .getAssociatedModelsMapWithFK(); for (String associatedTableName : associatedModelMap.keySet()) { String fkName = getForeignKeyColumnName(baseObj.getTableName()); rowsAffected += mDatabase.delete(associatedTableName, fkName + " = " + baseObj.getBaseObjId(), null); } return rowsAffected; } /** * Delete the referenced rows of baseObj in intermediate join * tables(Many2Many condition). * * @param baseObj * The record to delete. Now contains associations info. * @return The number of rows affected in all intermediate join tables. */ private int deleteAssociatedJoinTableRows(LitePalSupport baseObj) { int rowsAffected = 0; Set associatedTableNames = baseObj .getAssociatedModelsMapForJoinTable().keySet(); for (String associatedTableName : associatedTableNames) { String joinTableName = DBUtility.getIntermediateTableName( baseObj.getTableName(), associatedTableName); String fkName = getForeignKeyColumnName(baseObj.getTableName()); rowsAffected += mDatabase.delete(joinTableName, fkName + " = " + baseObj.getBaseObjId(), null); } return rowsAffected; } /** * Get all the associated tables of current model's table. Only used while * deleting by id. * * @return All the associated tables of current model's table. */ private List getForeignKeyTableToDelete() { if (foreignKeyTableToDelete == null) { foreignKeyTableToDelete = new ArrayList(); } return foreignKeyTableToDelete; } /** * Delete the generic data in generic tables while main data was deleted. * @param modelClass * Used to get the generic table name and value id column. * @param supportedGenericFields * List of all supported generic fields. * @param ids * The id array of models. */ private void deleteGenericData(Class modelClass, List supportedGenericFields, long... ids) { for (Field field : supportedGenericFields) { String tableName = DBUtility.getGenericTableName(modelClass.getName(), field.getName()); String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(modelClass.getName()); int maxExpressionCount = 500; // Prevent the where condition is too long int length = ids.length; int loopCount = (length - 1) / maxExpressionCount; for (int i = 0; i <= loopCount; i++) { StringBuilder whereClause = new StringBuilder(); boolean needOr = false; for (int j = maxExpressionCount * i; j < maxExpressionCount * (i + 1); j++) { if (j >= length) { break; } long id = ids[j]; if (needOr) { whereClause.append(" or "); } whereClause.append(genericValueIdColumnName).append(" = ").append(id); needOr = true; } if (!TextUtils.isEmpty(whereClause.toString())) { mDatabase.delete(tableName, whereClause.toString(), null); } } } } } ================================================ FILE: core/src/main/java/org/litepal/crud/DynamicExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.litepal.exceptions.LitePalSupportException; /** * This provides a send method to allow calling method in dynamic way. (Just * like Ruby, but not clean or easy as Ruby to use). * * @author Tony Green * @since 1.1 */ class DynamicExecutor { /** * Disable to create an instance of DynamicExecutor. */ private DynamicExecutor() { } /** * This method use java reflect API to execute method dynamically. Most * importantly, it could access the methods with private modifier to break * encapsulation. * * @param object * The object to invoke method. * @param methodName * The method name to invoke. * @param parameters * The parameters. * @param objectClass * Use objectClass to find method to invoke. * @param parameterTypes * The parameter types. * @return Returns the result of dynamically invoking method. * @throws SecurityException * @throws IllegalArgumentException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ static Object send(Object object, String methodName, Object[] parameters, Class objectClass, Class[] parameterTypes) throws SecurityException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { try { if (parameters == null) { parameters = new Object[] {}; } if (parameterTypes == null) { parameterTypes = new Class[] {}; } Method method = objectClass.getDeclaredMethod(methodName, parameterTypes); method.setAccessible(true); return method.invoke(object, parameters); } catch (NoSuchMethodException e) { throw new LitePalSupportException(LitePalSupportException.noSuchMethodException( objectClass.getSimpleName(), methodName), e); } } static void set(Object object, String fieldName, Object value, Class objectClass) throws SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException { Field objectField = objectClass.getDeclaredField(fieldName); objectField.setAccessible(true); objectField.set(object, value); } /** * This method use java reflect API to set field value dynamically. Most * importantly, it could access fields with private modifier to break * encapsulation. * * @param object * The object to access. * @param fieldName * The field name to access. * @param value * Assign this value to field. * @param objectClass * The class of object. * @throws SecurityException * @throws IllegalArgumentException * @throws IllegalAccessException */ static void setField(Object object, String fieldName, Object value, Class objectClass) throws SecurityException, IllegalArgumentException, IllegalAccessException { if (objectClass == LitePalSupport.class || objectClass == Object.class) { throw new LitePalSupportException(LitePalSupportException.noSuchFieldExceptioin( objectClass.getSimpleName(), fieldName)); } try { set(object, fieldName, value, objectClass); } catch (NoSuchFieldException e) { setField(object, fieldName, value, objectClass.getSuperclass()); } } /** * This method use java reflect API to get field value dynamically. Most * importantly, it could access fields with private modifier to break * encapsulation. * * @param object * The object to access. * @param fieldName * The field name to access. * @param objectClass * The class of object. * @throws SecurityException * @throws IllegalArgumentException * @throws IllegalAccessException */ static Object getField(Object object, String fieldName, Class objectClass) throws IllegalArgumentException, IllegalAccessException { if (objectClass == LitePalSupport.class || objectClass == Object.class) { throw new LitePalSupportException(LitePalSupportException.noSuchFieldExceptioin( objectClass.getSimpleName(), fieldName)); } try { Field objectField = objectClass.getDeclaredField(fieldName); objectField.setAccessible(true); return objectField.get(object); } catch (NoSuchFieldException e) { return getField(object, fieldName, objectClass.getSuperclass()); } } } ================================================ FILE: core/src/main/java/org/litepal/crud/LitePalSupport.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import android.database.sqlite.SQLiteDatabase; import org.litepal.Operator; import org.litepal.crud.async.SaveExecutor; import org.litepal.crud.async.UpdateOrDeleteExecutor; import org.litepal.exceptions.LitePalSupportException; import org.litepal.tablemanager.Connector; import org.litepal.util.BaseUtility; import org.litepal.util.DBUtility; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * LitePalSupport connects classes to SQLite database tables to establish an almost * zero-configuration persistence layer for applications. In the context of an * application, these classes are commonly referred to as models. Models can * also be connected to other models.
* LitePalSupport relies heavily on naming in that it uses class and association * names to establish mappings between respective database tables and foreign * key columns.
* Automated mapping between classes and tables, attributes and columns. * *
 * public class Person extends LitePalSupport {
 * 	private int id;
 * 	private String name;
 * 	private int age;
 * }
 * 
 * The Person class is automatically mapped to the table named "person",
 * which might look like this:
 * 
 * CREATE TABLE person (
 * 	id integer primary key autoincrement,
 * 	age integer, 
 * 	name text
 * );
 * 
* * @author Tony Green * @since 2.0 */ public class LitePalSupport { /** * Constant for MD5 encryption. */ protected static final String MD5 = "MD5"; /** * Constant for AES encryption. */ protected static final String AES = "AES"; /** * The identify of each model. LitePal will generate the value * automatically. Do not try to assign or modify it. */ long baseObjId; /** * A map contains all the associated models' id with M2O or O2O * associations. Each corresponding table of these models contains a foreign * key column. */ private Map> associatedModelsMapWithFK; /** * A map contains all the associated models' id with M2O or O2O association. * Each corresponding table of these models doesn't contain foreign key * column. Instead self model has a foreign key column in the corresponding * table. */ private Map associatedModelsMapWithoutFK; /** * A map contains all the associated models' id with M2M association. */ Map> associatedModelsMapForJoinTable; /** * When updating a model and the associations breaks between current model * and others, if current model holds a foreign key, it need to be cleared. * This list holds all the foreign key names that need to clear. */ private List listToClearSelfFK; /** * When updating a model and the associations breaks between current model * and others, clear all the associated models' foreign key value if it * exists. This list holds all the associated table names that need to * clear. */ private List listToClearAssociatedFK; /** * A list holds all the field names which need to be updated into default * value of model. */ private List fieldsToSetToDefault; /** * Deletes the record in the database. The record must be saved already.
* The data in other tables which is referenced with the record will be * removed too. * *
	 * Person person;
	 * ....
	 * if (person.isSaved()) {
	 * 		person.delete();
	 * }
	 * 
* * @return The number of rows affected. Including cascade delete rows. */ public int delete() { synchronized (LitePalSupport.class) { SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { DeleteHandler deleteHandler = new DeleteHandler(db); int rowsAffected = deleteHandler.onDelete(this); baseObjId = 0; db.setTransactionSuccessful(); return rowsAffected; } finally { db.endTransaction(); } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public UpdateOrDeleteExecutor deleteAsync() { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = delete(); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Updates the corresponding record by id. Use setXxx to decide which * columns to update. * *
	 * Person person = new Person();
	 * person.setName("Jim");
	 * person.update(1);
	 * 
* * This means that the name of record 1 will be updated into Jim.
* * Note: 1. If you set a default value to a field, the corresponding * column won't be updated. Use {@link #setToDefault(String)} to update * columns into default value. 2. This method couldn't update foreign key in * database. So do not use setXxx to set associations between models. * * @param id * Which record to update. * @return The number of rows affected. */ public int update(long id) { synchronized (LitePalSupport.class) { SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); int rowsAffected = updateHandler.onUpdate(this, id); getFieldsToSetToDefault().clear(); db.setTransactionSuccessful(); return rowsAffected; } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } finally { db.endTransaction(); } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public UpdateOrDeleteExecutor updateAsync(final long id) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = update(id); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * *
	 * Person person = new Person();
	 * person.setName("Jim");
	 * person.updateAll("name = ?", "Tom");
	 * 
* * This means that all the records which name is Tom will be updated into * Jim.
* * Note: 1. If you set a default value to a field, the corresponding * column won't be updated. Use {@link #setToDefault(String)} to update * columns into default value. 2. This method couldn't update foreign key in * database. So do not use setXxx to set associations between models. * * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public int updateAll(String... conditions) { synchronized (LitePalSupport.class) { SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); int rowsAffected = updateHandler.onUpdateAll(this, conditions); getFieldsToSetToDefault().clear(); db.setTransactionSuccessful(); return rowsAffected; } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } finally { db.endTransaction(); } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public UpdateOrDeleteExecutor updateAllAsync(final String... conditions) { final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final int rowsAffected = updateAll(conditions); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(rowsAffected); } }); } } } }; executor.submit(runnable); return executor; } /** * Saves the model.
* *
	 * Person person = new Person();
	 * person.setName("Tom");
	 * person.setAge(22);
	 * person.save();
	 * 
* * If the model is a new record gets created in the database, otherwise the * existing record gets updated.
* If saving process failed by any accident, the whole action will be * cancelled and your database will be rolled back.
* If the model has a field named id or _id and field type is int or long, * the id value generated by database will assign to it after the model is * saved.
* Note that if the associated models of this model is already saved. The * associations between them will be built automatically in database after * it saved. * * @return If the model is saved successfully, return true. Any exception * happens, return false. */ public boolean save() { try { saveThrows(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public SaveExecutor saveAsync() { final SaveExecutor executor = new SaveExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final boolean success = save(); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(success); } }); } } } }; executor.submit(runnable); return executor; } /** * Saves the model.
* *
	 * Person person = new Person();
	 * person.setName("Tom");
	 * person.setAge(22);
	 * person.saveThrows();
	 * 
* * If the model is a new record gets created in the database, otherwise the * existing record gets updated.
* If saving process failed by any accident, the whole action will be * cancelled and your database will be rolled back and throws * {@link LitePalSupportException}
* If the model has a field named id or _id and field type is int or long, * the id value generated by database will assign to it after the model is * saved.
* Note that if the associated models of this model is already saved. The * associations between them will be built automatically in database after * it saved. * * @throws LitePalSupportException */ public void saveThrows() { synchronized (LitePalSupport.class) { SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { SaveHandler saveHandler = new SaveHandler(db); saveHandler.onSave(this); clearAssociatedData(); db.setTransactionSuccessful(); } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } finally { db.endTransaction(); } } } /** * Save the model if the conditions data not exist, or update the matching models if the conditions data exist.
* *
     * Person person = new Person();
     * person.setName("Tom");
     * person.setAge(22);
     * person.saveOrUpdate("name = ?", "Tom");
     * 
* * If person table doesn't have a name with Tom, a new record gets created in the database, * otherwise all records which names are Tom will be updated.
* If saving process failed by any accident, the whole action will be * cancelled and your database will be rolled back.
* If the model has a field named id or _id and field type is int or long, * the id value generated by database will assign to it after the model is * saved.
* Note that if the associated models of this model is already saved. The * associations between them will be built automatically in database after * it saved. * * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return If the model saved or updated successfully, return true. Otherwise return false. */ @SuppressWarnings("unchecked") public boolean saveOrUpdate(String... conditions) { synchronized (LitePalSupport.class) { if (conditions == null || conditions.length == 0) { return save(); } List list = (List) Operator.where(conditions).find(getClass()); if (list.isEmpty()) { return save(); } else { SQLiteDatabase db = Connector.getDatabase(); db.beginTransaction(); try { for (LitePalSupport support : list) { baseObjId = support.getBaseObjId(); SaveHandler saveHandler = new SaveHandler(db); saveHandler.onSave(this); clearAssociatedData(); } db.setTransactionSuccessful(); return true; } catch (Exception e) { e.printStackTrace(); return false; } finally { db.endTransaction(); } } } } /** * This method is deprecated and will be removed in the future releases. * Handle async db operation in your own logic instead. */ @Deprecated public SaveExecutor saveOrUpdateAsync(final String... conditions) { final SaveExecutor executor = new SaveExecutor(); Runnable runnable = new Runnable() { @Override public void run() { synchronized (LitePalSupport.class) { final boolean success = saveOrUpdate(conditions); if (executor.getListener() != null) { Operator.getHandler().post(new Runnable() { @Override public void run() { executor.getListener().onFinish(success); } }); } } } }; executor.submit(runnable); return executor; } /** * Current model is saved or not. * * @return If saved return true, or return false. */ public boolean isSaved() { return baseObjId > 0; } /** * It model is saved, clear the saved state and model becomes unsaved. Otherwise nothing will happen. */ public void clearSavedState() { baseObjId = 0; } /** * When updating database with {@link LitePalSupport#update(long)}, you must * use this method to update a field into default value. Use setXxx with * default value of the model won't update anything.
* * @param fieldName * The name of field to update into default value. */ public void setToDefault(String fieldName) { getFieldsToSetToDefault().add(fieldName); } /** * Assigns value to baseObjId. This will override the original value. Never call this method * unless you know exactly what you are doing. * @param baseObjId * Assigns value to baseObjId. */ public void assignBaseObjId(long baseObjId) { this.baseObjId = baseObjId; } /** * Disable developers to create instance of LitePalSupport directly. They * should inherit this class with subclasses and operate on them. */ protected LitePalSupport() { } /** * Get the baseObjId of this model if it's useful for developers. It's for * system use usually. Do not try to assign or modify it. * * @return The base object id. */ protected long getBaseObjId() { return baseObjId; } /** * Get the full class name of self. * * @return The full class name of self. */ protected String getClassName() { return getClass().getName(); } /** * Get the corresponding table name of current model. * * @return The corresponding table name of current model. */ protected String getTableName() { return BaseUtility.changeCase(DBUtility.getTableNameByClassName(getClassName())); } /** * Get the list which holds all field names to update them into default * value of model in database. * * @return List holds all the field names which need to be updated into * default value of model. */ List getFieldsToSetToDefault() { if (fieldsToSetToDefault == null) { fieldsToSetToDefault = new ArrayList(); } return fieldsToSetToDefault; } /** * Add the id of an associated model into self model's associatedIdsWithFK * map. The associated model has a foreign key column in the corresponding * table. * * @param associatedTableName * The table name of associated model. * @param associatedId * The {@link #baseObjId} of associated model after it is saved. */ void addAssociatedModelWithFK(String associatedTableName, long associatedId) { Set associatedIdsWithFKSet = getAssociatedModelsMapWithFK().get(associatedTableName); if (associatedIdsWithFKSet == null) { associatedIdsWithFKSet = new HashSet(); associatedIdsWithFKSet.add(associatedId); associatedModelsMapWithFK.put(associatedTableName, associatedIdsWithFKSet); } else { associatedIdsWithFKSet.add(associatedId); } } /** * Get the associated model's map of self model. It can be used for * associations actions of CRUD. The key is the name of associated model. * The value is a List of id of associated models. * * @return An associated model's map to update all the foreign key columns * of associated models' table with self model's id. */ Map> getAssociatedModelsMapWithFK() { if (associatedModelsMapWithFK == null) { associatedModelsMapWithFK = new HashMap>(); } return associatedModelsMapWithFK; } /** * Add the id of an associated model into self model's associatedIdsM2M map. * * @param associatedModelName * The name of associated model. * @param associatedId * The id of associated model. */ void addAssociatedModelForJoinTable(String associatedModelName, long associatedId) { List associatedIdsM2MSet = getAssociatedModelsMapForJoinTable().get( associatedModelName); if (associatedIdsM2MSet == null) { associatedIdsM2MSet = new ArrayList(); associatedIdsM2MSet.add(associatedId); associatedModelsMapForJoinTable.put(associatedModelName, associatedIdsM2MSet); } else { associatedIdsM2MSet.add(associatedId); } } /** * Add an empty Set into {@link #associatedModelsMapForJoinTable} with * associated model name as key. Might be useful when comes to update * intermediate join table. * * @param associatedModelName * The name of associated model. */ void addEmptyModelForJoinTable(String associatedModelName) { List associatedIdsM2MSet = getAssociatedModelsMapForJoinTable().get( associatedModelName); if (associatedIdsM2MSet == null) { associatedIdsM2MSet = new ArrayList(); associatedModelsMapForJoinTable.put(associatedModelName, associatedIdsM2MSet); } } /** * Get the associated model's map for intermediate join table. It is used to * save values into intermediate join table. The key is the name of * associated model. The value is the id of associated model. * * @return An associated model's map to save values into intermediate join * table */ Map> getAssociatedModelsMapForJoinTable() { if (associatedModelsMapForJoinTable == null) { associatedModelsMapForJoinTable = new HashMap>(); } return associatedModelsMapForJoinTable; } /** * Add the id of an associated model into self model's association * collection. The associated model doesn't have a foreign key column in the * corresponding table. Instead self model has a foreign key column in the * corresponding table. * * @param associatedTableName * The simple class name of associated model. * @param associatedId * The {@link #baseObjId} of associated model after it is saved. */ void addAssociatedModelWithoutFK(String associatedTableName, long associatedId) { getAssociatedModelsMapWithoutFK().put(associatedTableName, associatedId); } /** * Get the associated model's map of self model. It can be used for * associations actions of CRUD. The key is the name of associated model's * table. The value is the id of associated model. * * @return An associated model's map to save self model with foreign key. */ Map getAssociatedModelsMapWithoutFK() { if (associatedModelsMapWithoutFK == null) { associatedModelsMapWithoutFK = new HashMap(); } return associatedModelsMapWithoutFK; } /** * Add a foreign key name into the clear list. * * @param foreignKeyName * The name of foreign key. */ void addFKNameToClearSelf(String foreignKeyName) { List list = getListToClearSelfFK(); if (!list.contains(foreignKeyName)) { list.add(foreignKeyName); } } /** * Get the foreign key name list to clear foreign key value in current * model's table. * * @return The list of foreign key names to clear in current model's table. */ List getListToClearSelfFK() { if (listToClearSelfFK == null) { listToClearSelfFK = new ArrayList(); } return listToClearSelfFK; } /** * Add an associated table name into the list to clear. * * @param associatedTableName * The name of associated table. */ void addAssociatedTableNameToClearFK(String associatedTableName) { List list = getListToClearAssociatedFK(); if (!list.contains(associatedTableName)) { list.add(associatedTableName); } } /** * Get the associated table names list which need to clear their foreign key * values. * * @return The list with associated table names to clear foreign key values. */ List getListToClearAssociatedFK() { if (listToClearAssociatedFK == null) { listToClearAssociatedFK = new ArrayList(); } return listToClearAssociatedFK; } /** * Clear all the data for storing associated models' data. */ void clearAssociatedData() { clearIdOfModelWithFK(); clearIdOfModelWithoutFK(); clearIdOfModelForJoinTable(); clearFKNameList(); } /** * Clear all the data in {@link #associatedModelsMapWithFK}. */ private void clearIdOfModelWithFK() { for (String associatedModelName : getAssociatedModelsMapWithFK().keySet()) { associatedModelsMapWithFK.get(associatedModelName).clear(); } associatedModelsMapWithFK.clear(); } /** * Clear all the data in {@link #associatedModelsMapWithoutFK}. */ private void clearIdOfModelWithoutFK() { getAssociatedModelsMapWithoutFK().clear(); } /** * Clear all the data in {@link #associatedModelsMapForJoinTable}. */ private void clearIdOfModelForJoinTable() { for (String associatedModelName : getAssociatedModelsMapForJoinTable().keySet()) { associatedModelsMapForJoinTable.get(associatedModelName).clear(); } associatedModelsMapForJoinTable.clear(); } /** * Clear all the data in {@link #listToClearSelfFK}. */ private void clearFKNameList() { getListToClearSelfFK().clear(); getListToClearAssociatedFK().clear(); } } ================================================ FILE: core/src/main/java/org/litepal/crud/Many2ManyAnalyzer.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import org.litepal.LitePalBase; import org.litepal.crud.model.AssociationsInfo; import org.litepal.tablemanager.Connector; import org.litepal.util.BaseUtility; import org.litepal.util.DBUtility; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; /** * Deals analysis work when comes to two models are associated with Many2Many * associations. * * @author Tony Green * @since 1.1 */ public class Many2ManyAnalyzer extends AssociationsAnalyzer { /** * Analyzing the AssociationInfo. It will help baseObj assign the necessary * values automatically. If the two associated models have bidirectional * associations in class files but developer has only build unidirectional * associations in models, it will force to build the bidirectional * associations. Besides the * {@link LitePalSupport#addAssociatedModelForJoinTable(String, long)} will be called * here to put right values into tables. * * @param baseObj * The baseObj currently want to persist or update. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ void analyze(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Collection associatedModels = getAssociatedModels(baseObj, associationInfo); declareAssociations(baseObj, associationInfo); if (associatedModels != null) { for (LitePalSupport associatedModel : associatedModels) { Collection tempCollection = getReverseAssociatedModels( associatedModel, associationInfo); Collection reverseAssociatedModels = checkAssociatedModelCollection( tempCollection, associationInfo.getAssociateSelfFromOtherModel()); addNewModelForAssociatedModel(reverseAssociatedModels, baseObj); setReverseAssociatedModels(associatedModel, associationInfo, reverseAssociatedModels); dealAssociatedModel(baseObj, associatedModel); } } } /** * This add an empty set for {@link LitePalSupport#associatedModelsMapForJoinTable}. * Might use for updating intermediate join table. * * @param baseObj * The baseObj currently want to persist or update. * @param associationInfo * To get associated table name from. */ private void declareAssociations(LitePalSupport baseObj, AssociationsInfo associationInfo) { baseObj.addEmptyModelForJoinTable(getAssociatedTableName(associationInfo)); } /** * Force to build bidirectional associations for the associated model. If it * has already built, ignoring the rest process. * * @param associatedModelCollection * The associated models collection of the associated model. Add * self model into it if it doesn't contain self model yet. * @param baseObj * The baseObj currently want to persist or update. */ private void addNewModelForAssociatedModel(Collection associatedModelCollection, LitePalSupport baseObj) { if (!associatedModelCollection.contains(baseObj)) { associatedModelCollection.add(baseObj); } } /** * First of all the associated model need to be saved already, or nothing * will be executed below. Then add the id of associated model into * {@link LitePalSupport#associatedModelsMapForJoinTable} for * inserting value into intermediate join table after baseObj is saved. * * @param baseObj * The baseObj currently want to persist or update. * @param associatedModel * The associated model of baseObj. */ private void dealAssociatedModel(LitePalSupport baseObj, LitePalSupport associatedModel) { if (associatedModel.isSaved()) { baseObj.addAssociatedModelForJoinTable(associatedModel.getTableName(), associatedModel.getBaseObjId()); } } /** * Get the associated table name by {@link org.litepal.crud.model.AssociationsInfo} after case * changed. * * @param associationInfo * To get the associated table name from. * @return The name of associated table with changed case. */ private String getAssociatedTableName(AssociationsInfo associationInfo) { return BaseUtility.changeCase(DBUtility.getTableNameByClassName(associationInfo .getAssociatedClassName())); } /** * Check if the associations between self model and associated model is * already saved into intermediate join table.
* Make sure baseObj and associatedModel are saved already, or the result * might be wrong. * * @param baseObj * The baseObj currently want to persist or update. * @param associatedModel * The associated model of baseObj. * @return If the associations between them is saved into intermediate join * table, return true. Otherwise return false. */ @SuppressWarnings("unused") @Deprecated private boolean isDataExists(LitePalSupport baseObj, LitePalSupport associatedModel) { boolean exists = false; SQLiteDatabase db = Connector.getDatabase(); Cursor cursor = null; try { cursor = db.query(getJoinTableName(baseObj, associatedModel), null, getSelection(baseObj, associatedModel), getSelectionArgs(baseObj, associatedModel), null, null, null); exists = cursor.getCount() > 0; } catch (Exception e) { e.printStackTrace(); return true; } finally { cursor.close(); } return exists; } /** * Build the selection for querying the data in table. Column names are the * table names with _id as suffix. * * @param baseObj * The baseObj currently want to persist or update. * @param associatedModel * The associated model of baseObj. * @return The selection clause for querying data. */ private String getSelection(LitePalSupport baseObj, LitePalSupport associatedModel) { StringBuilder where = new StringBuilder(); where.append(getForeignKeyColumnName(baseObj.getTableName())); where.append(" = ? and "); where.append(getForeignKeyColumnName(associatedModel.getTableName())); where.append(" = ?"); return where.toString(); } /** * Build the selection arguments to fill selection clause. * * @param baseObj * The baseObj currently want to persist or update. * @param associatedModel * The associated model of baseObj. * @return The selection arguments with the id of baseObj and * associatedModel to fill. */ private String[] getSelectionArgs(LitePalSupport baseObj, LitePalSupport associatedModel) { return new String[] { String.valueOf(baseObj.getBaseObjId()), String.valueOf(associatedModel.getBaseObjId()) }; } /** * Get the intermediate join table name for self model and associated model. * * @param baseObj * The baseObj currently want to persist or update. * @param associatedModel * The associated model of baseObj. * @return The intermediate join table name. */ private String getJoinTableName(LitePalSupport baseObj, LitePalSupport associatedModel) { return getIntermediateTableName(baseObj, associatedModel.getTableName()); } } ================================================ FILE: core/src/main/java/org/litepal/crud/Many2OneAnalyzer.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import org.litepal.LitePalBase; import org.litepal.crud.model.AssociationsInfo; import org.litepal.util.DBUtility; /** * Deals analysis work when comes to two models are associated with Many2One * associations. * * @author Tony Green * @since 1.1 */ class Many2OneAnalyzer extends AssociationsAnalyzer { /** * Analyzing the AssociationInfo. It will help baseObj assign the necessary * values automatically. If the two associated models have bidirectional * associations in class files but developer has only build unidirectional * associations in models, it will force to build the bidirectional * associations. Besides * {@link LitePalSupport#addAssociatedModelWithFK(String, long)} and * {@link LitePalSupport#addAssociatedModelWithoutFK(String, long)} will be * called here to put right values into tables. * * @param baseObj * The baseObj currently want to persist. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ void analyze(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (baseObj.getClassName().equals(associationInfo.getClassHoldsForeignKey())) { analyzeManySide(baseObj, associationInfo); } else { analyzeOneSide(baseObj, associationInfo); } } /** * When it's on the M side. Get the associated model first, then use it to * get the associated model collection on the O side. Initialize the * collection by calling * {@link #checkAssociatedModelCollection(java.util.Collection, java.lang.reflect.Field)} * and calling * {@link #dealAssociatedModelOnManySide(java.util.Collection, LitePalSupport, LitePalSupport)} * to set foreign key. * * @param baseObj * The baseObj currently want to persist or update. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ private void analyzeManySide(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { LitePalSupport associatedModel = getAssociatedModel(baseObj, associationInfo); if (associatedModel != null) { // now it's m2o bidirectional association. Collection tempCollection = getReverseAssociatedModels(associatedModel, associationInfo); Collection reverseAssociatedModels = checkAssociatedModelCollection( tempCollection, associationInfo.getAssociateSelfFromOtherModel()); setReverseAssociatedModels(associatedModel, associationInfo, reverseAssociatedModels); dealAssociatedModelOnManySide(reverseAssociatedModels, baseObj, associatedModel); } else { mightClearFKValue(baseObj, associationInfo); } } /** * When it's on the O side. Get the associated model collection first, then * iterate all the associated models. Each associated model calls * {@link #buildBidirectionalAssociations(LitePalSupport, LitePalSupport, org.litepal.crud.model.AssociationsInfo)} * to build bidirectional association if they haven't built yet. Then calls * {@link #dealAssociatedModelOnOneSide(LitePalSupport, LitePalSupport)} to set * foreign key. * * @param baseObj * The baseObj currently want to persist. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ private void analyzeOneSide(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Collection associatedModels = getAssociatedModels(baseObj, associationInfo); if (associatedModels == null || associatedModels.isEmpty()) { String tableName = DBUtility.getTableNameByClassName(associationInfo .getAssociatedClassName()); baseObj.addAssociatedTableNameToClearFK(tableName); return; } for (LitePalSupport associatedModel : associatedModels) { buildBidirectionalAssociations(baseObj, associatedModel, associationInfo); dealAssociatedModelOnOneSide(baseObj, associatedModel); } } /** * Check if the baseObj is already existed in the associatedModels * collection. If not add baseObj into the collection. Then if the * associated model is saved, add its' name and id to baseObj by calling * {@link LitePalSupport#addAssociatedModelWithFK(String, long)}. * * @param associatedModels * The associated model collection. * @param baseObj * The baseObj currently want to persist. * @param associatedModel * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. */ private void dealAssociatedModelOnManySide(Collection associatedModels, LitePalSupport baseObj, LitePalSupport associatedModel) { if (!associatedModels.contains(baseObj)) { associatedModels.add(baseObj); } if (associatedModel.isSaved()) { baseObj.addAssociatedModelWithoutFK(associatedModel.getTableName(), associatedModel.getBaseObjId()); } } /** * Deals with associated model on one side. * * @param baseObj * The baseObj currently want to persist. * @param associatedModel * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. */ private void dealAssociatedModelOnOneSide(LitePalSupport baseObj, LitePalSupport associatedModel) { dealsAssociationsOnTheSideWithoutFK(baseObj, associatedModel); } } ================================================ FILE: core/src/main/java/org/litepal/crud/One2OneAnalyzer.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import java.lang.reflect.InvocationTargetException; import org.litepal.LitePalBase; import org.litepal.crud.model.AssociationsInfo; import org.litepal.util.DBUtility; /** * Deals analysis work when comes to two models are associated with One2One * associations. * * @author Tony Green * @since 1.1 */ public class One2OneAnalyzer extends AssociationsAnalyzer { /** * Analyzing the AssociationInfo. It will help baseObj assign the necessary * values automatically. If the two associated models have bidirectional * associations in class files but developer has only build unidirectional * associations in models, it will force to build the bidirectional * associations. Besides * {@link LitePalSupport#addAssociatedModelWithFK(String, long)} and * {@link LitePalSupport#addAssociatedModelWithoutFK(String, long)} will be * called here to put right values into tables. * * @param baseObj * The baseObj currently want to persist or update. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws java.lang.reflect.InvocationTargetException */ void analyze(LitePalSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { LitePalSupport associatedModel = getAssociatedModel(baseObj, associationInfo); if (associatedModel != null) { buildBidirectionalAssociations(baseObj, associatedModel, associationInfo); dealAssociatedModel(baseObj, associatedModel, associationInfo); } else { String tableName = DBUtility.getTableNameByClassName(associationInfo .getAssociatedClassName()); baseObj.addAssociatedTableNameToClearFK(tableName); } } /** * Check the association type. If it's bidirectional association, calls * {@link #bidirectionalCondition(LitePalSupport, LitePalSupport)}. If it's * unidirectional association, calls * {@link #unidirectionalCondition(LitePalSupport, LitePalSupport)}. * * @param baseObj * The baseObj currently want to persist. * @param associatedModel * The associated model of baseObj. * @param associationInfo * The associated info analyzed by * {@link LitePalBase#getAssociationInfo(String)}. */ private void dealAssociatedModel(LitePalSupport baseObj, LitePalSupport associatedModel, AssociationsInfo associationInfo) { if (associationInfo.getAssociateSelfFromOtherModel() != null) { bidirectionalCondition(baseObj, associatedModel); } else { unidirectionalCondition(baseObj, associatedModel); } } /** * Deals bidirectional association condition. If associated model is saved, * add its' name and id to baseObj by calling * {@link LitePalSupport#addAssociatedModelWithFK(String, long)}. Add its' name * and id to baseObj by calling * {@link LitePalSupport#addAssociatedModelWithoutFK(String, long)}. * * @param baseObj * The baseObj currently want to persist. * @param associatedModel * The associated model of baseObj. */ private void bidirectionalCondition(LitePalSupport baseObj, LitePalSupport associatedModel) { if (associatedModel.isSaved()) { // use to update associated table after saving baseObj.addAssociatedModelWithFK(associatedModel.getTableName(), associatedModel.getBaseObjId()); // use to add foreign key value while saving baseObj.addAssociatedModelWithoutFK(associatedModel.getTableName(), associatedModel.getBaseObjId()); } } /** * Deals unidirectional associations condition. * * @param baseObj * The baseObj currently want to persist. * @param associatedModel * The associated model of baseObj. */ private void unidirectionalCondition(LitePalSupport baseObj, LitePalSupport associatedModel) { dealsAssociationsOnTheSideWithoutFK(baseObj, associatedModel); } } ================================================ FILE: core/src/main/java/org/litepal/crud/QueryHandler.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import java.util.List; import org.litepal.util.BaseUtility; import org.litepal.util.DBUtility; import android.database.sqlite.SQLiteDatabase; /** * This is a component under LitePalSupport. It deals with query stuff as primary * task. * * @author Tony Green * @since 1.1 */ public class QueryHandler extends DataHandler { /** * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not * allow to create instance of QueryHandler out of CRUD package. * * @param db * The instance of SQLiteDatabase. */ public QueryHandler(SQLiteDatabase db) { mDatabase = db; } /** * The open interface for other classes in CRUD package to query a record * based on id. If the result set is empty, gives null back. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ public T onFind(Class modelClass, long id, boolean isEager) { List dataList = query(modelClass, null, "id = ?", new String[] { String.valueOf(id) }, null, null, null, null, getForeignKeyAssociations(modelClass.getName(), isEager)); if (dataList.size() > 0) { return dataList.get(0); } return null; } /** * The open interface for other classes in CRUD package to query the first * record in a table. If the result set is empty, gives null back. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ public T onFindFirst(Class modelClass, boolean isEager) { List dataList = query(modelClass, null, null, null, null, null, "id", "1", getForeignKeyAssociations(modelClass.getName(), isEager)); if (dataList.size() > 0) { return dataList.get(0); } return null; } /** * The open interface for other classes in CRUD package to query the last * record in a table. If the result set is empty, gives null back. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ public T onFindLast(Class modelClass, boolean isEager) { List dataList = query(modelClass, null, null, null, null, null, "id desc", "1", getForeignKeyAssociations(modelClass.getName(), isEager)); if (dataList.size() > 0) { return dataList.get(0); } return null; } /** * The open interface for other classes in CRUD package to query multiple * records by an id array. Pass no ids means query all rows. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ public List onFindAll(Class modelClass, boolean isEager, long... ids) { List dataList; if (isAffectAllLines(ids)) { dataList = query(modelClass, null, null, null, null, null, "id", null, getForeignKeyAssociations(modelClass.getName(), isEager)); } else { dataList = query(modelClass, null, getWhereOfIdsWithOr(ids), null, null, null, "id", null, getForeignKeyAssociations(modelClass.getName(), isEager)); } return dataList; } /** * The open interface for other classes in CRUD package to query multiple * records by parameters. * * @param modelClass * Which table to query and the object type to return as a list. * @param columns * A String array of which columns to return. Passing null will * return all columns. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @param orderBy * How to order the rows, formatted as an SQL ORDER BY clause. * Passing null will use the default sort order, which may be * unordered. * @param limit * Limits the number of rows returned by the query, formatted as * LIMIT clause. * @param isEager * True to load the associated models, false not. * @return An object list with found data from database, or an empty list. */ public List onFind(Class modelClass, String[] columns, String[] conditions, String orderBy, String limit, boolean isEager) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } orderBy = DBUtility.convertOrderByClauseToValidName(orderBy); return query(modelClass, columns, getWhereClause(conditions), getWhereArgs(conditions), null, null, orderBy, limit, getForeignKeyAssociations(modelClass.getName(), isEager)); } /** * The open interface for other classes in CRUD package to Count the * records. * * @param tableName * Which table to query from. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return Count of the specified table. */ public int onCount(String tableName, String[] conditions) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } return mathQuery(tableName, new String[] { "count(1)" }, conditions, int.class); } /** * The open interface for other classes in CRUD package to calculate the * average value on a given column. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return The average value on a given column. */ public double onAverage(String tableName, String column, String[] conditions) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } return mathQuery(tableName, new String[] { "avg(" + column + ")" }, conditions, double.class); } /** * The open interface for other classes in CRUD package to calculate the * maximum value on a given column. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @param type * The type of the based on column. * @return The maximum value on a given column. */ public T onMax(String tableName, String column, String[] conditions, Class type) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } return mathQuery(tableName, new String[] { "max(" + column + ")" }, conditions, type); } /** * The open interface for other classes in CRUD package to calculate the * minimum value on a given column. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @param type * The type of the based on column. * @return The minimum value on a given column. */ public T onMin(String tableName, String column, String[] conditions, Class type) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } return mathQuery(tableName, new String[] { "min(" + column + ")" }, conditions, type); } /** * The open interface for other classes in CRUD package to calculate the sum * of values on a given column. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @param type * The type of the based on column. * @return The sum value on a given column. */ public T onSum(String tableName, String column, String[] conditions, Class type) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } return mathQuery(tableName, new String[] { "sum(" + column + ")" }, conditions, type); } } ================================================ FILE: core/src/main/java/org/litepal/crud/SaveHandler.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import org.litepal.annotation.Encrypt; import org.litepal.crud.model.AssociationsInfo; import org.litepal.exceptions.LitePalSupportException; import org.litepal.util.DBUtility; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import static org.litepal.util.BaseUtility.changeCase; /** * This is a component under LitePalSupport. It deals with the saving stuff as * primary task. All the implementation based on the java reflection API and * Android SQLiteDatabase API. It will persist the model class into table. If * there're some associated models already persisted, it will build the * associations in database automatically between the current model and the * associated models. * * @author Tony Green * @since 1.1 */ public class SaveHandler extends DataHandler { private ContentValues values; /** * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not * allow to create instance of SaveHandler out of CRUD package. * * @param db * The instance of SQLiteDatabase. */ public SaveHandler(SQLiteDatabase db) { values = new ContentValues(); mDatabase = db; } /** * The open interface for other classes in CRUD package to save a model. It * is called when a model class calls the save method. First of all, the * passed in baseObj will be saved into database. Then LitePal will analyze * the associations. If there're associated models detected, each associated * model which is persisted will build association with current model in * database. * * @param baseObj * Current model to persist. */ void onSave(LitePalSupport baseObj) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { String className = baseObj.getClassName(); List supportedFields = getSupportedFields(className); List supportedGenericFields = getSupportedGenericFields(className); Collection associationInfos = getAssociationInfo(className); if (!baseObj.isSaved()) { analyzeAssociatedModels(baseObj, associationInfos); doSaveAction(baseObj, supportedFields, supportedGenericFields); analyzeAssociatedModels(baseObj, associationInfos); } else { analyzeAssociatedModels(baseObj, associationInfos); doUpdateAction(baseObj, supportedFields, supportedGenericFields); } } /** * The open interface for other classes in CRUD package to save a model * collection. It is called when developer calls * {@link org.litepal.Operator#saveAll(java.util.Collection)}. Each model in the collection * will be persisted. If there're associated models detected, each * associated model which is persisted will build association with current * model in database. * * @param collection * Holds all models to persist. */ public void onSaveAll(Collection collection) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (collection != null && collection.size() > 0) { LitePalSupport[] array = collection.toArray(new LitePalSupport[0]); LitePalSupport firstObj = array[0]; String className = firstObj.getClassName(); List supportedFields = getSupportedFields(className); List supportedGenericFields = getSupportedGenericFields(className); Collection associationInfos = getAssociationInfo(className); for (LitePalSupport baseObj : array) { if (!baseObj.isSaved()) { analyzeAssociatedModels(baseObj, associationInfos); doSaveAction(baseObj, supportedFields, supportedGenericFields); analyzeAssociatedModels(baseObj, associationInfos); } else { analyzeAssociatedModels(baseObj, associationInfos); doUpdateAction(baseObj, supportedFields, supportedGenericFields); } baseObj.clearAssociatedData(); } } } /** * Persisting model class into database happens here. But first * {@link #beforeSave(LitePalSupport, java.util.List, android.content.ContentValues)} will be called to * put the values for ContentValues. When the model is saved, * {@link #afterSave(LitePalSupport, java.util.List, java.util.List, long)} will be called to do stuffs * after model is saved. Note that SaveSupport won't help with id. Any * developer who wants to set value to id will be ignored here. The value of * id will be generated by SQLite automatically. * * @param baseObj * Current model to persist. * @param supportedFields * List of all supported fields. * @param supportedGenericFields * List of all supported generic fields. */ private void doSaveAction(LitePalSupport baseObj, List supportedFields, List supportedGenericFields) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { values.clear(); beforeSave(baseObj, supportedFields, values); long id = saving(baseObj, values); afterSave(baseObj, supportedFields, supportedGenericFields, id); } /** * Before the self model is saved, it will be analyzed first. Put all the * data contained by the model into ContentValues, including the fields * value and foreign key value. * * @param baseObj * Current model to persist. * @param supportedFields * List of all supported fields. * @param values * To store data of current model for persisting. */ private void beforeSave(LitePalSupport baseObj, List supportedFields, ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { putFieldsValue(baseObj, supportedFields, values); putForeignKeyValue(values, baseObj); } /** * Calling {@link android.database.sqlite.SQLiteDatabase#insert(String, String, android.content.ContentValues)} to * persist the current model. * * @param baseObj * Current model to persist. * @param values * To store data of current model for persisting. * @return The row ID of the newly inserted row, or -1 if an error occurred. */ private long saving(LitePalSupport baseObj, ContentValues values) { if (values.size() == 0) { values.putNull("id"); } return mDatabase.insert(baseObj.getTableName(), null, values); } /** * After the model is saved, do the extra work that need to do. * * @param baseObj * Current model that is persisted. * @param supportedFields * List of all supported fields. * @param supportedGenericFields * List of all supported generic fields. * @param id * The current model's id. */ private void afterSave(LitePalSupport baseObj, List supportedFields, List supportedGenericFields, long id) throws IllegalAccessException, InvocationTargetException { throwIfSaveFailed(id); assignIdValue(baseObj, getIdField(supportedFields), id); updateGenericTables(baseObj, supportedGenericFields, id); updateAssociatedTableWithFK(baseObj); insertIntermediateJoinTableValue(baseObj, false); } /** * When a model is associated with two different models. * * @param baseObj * The class of base object. * @param supportedFields * List of all supported fields. * @param supportedGenericFields * List of all supported generic fields. */ private void doUpdateAction(LitePalSupport baseObj, List supportedFields, List supportedGenericFields) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { values.clear(); beforeUpdate(baseObj, supportedFields, values); updating(baseObj, values); afterUpdate(baseObj, supportedGenericFields); } /** * Before updating model, it will be analyzed first. Put all the data * contained by the model into ContentValues, including the fields value and * foreign key value. If the associations between models has been removed. * The foreign key value in database should be cleared too. * * @param baseObj * Current model to update. * @param supportedFields * List of all supported fields. * @param values * To store data of current model for updating. */ private void beforeUpdate(LitePalSupport baseObj, List supportedFields, ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { putFieldsValue(baseObj, supportedFields, values); putForeignKeyValue(values, baseObj); for (String fkName : baseObj.getListToClearSelfFK()) { values.putNull(fkName); } } /** * Calling * {@link android.database.sqlite.SQLiteDatabase#update(String, android.content.ContentValues, String, String[])} to * update the current model. * * @param baseObj * Current model to update. * @param values * To store data of current model for updating. */ private void updating(LitePalSupport baseObj, ContentValues values) { if (values.size() > 0) { mDatabase.update(baseObj.getTableName(), values, "id = ?", new String[] { String.valueOf(baseObj.getBaseObjId()) }); } } /** * After the model is updated, do the extra work that need to do. * * @param baseObj * Current model that is updated. * @param supportedGenericFields * List of all supported generic fields. */ private void afterUpdate(LitePalSupport baseObj, List supportedGenericFields) throws InvocationTargetException, IllegalAccessException { updateGenericTables(baseObj, supportedGenericFields, baseObj.getBaseObjId()); updateAssociatedTableWithFK(baseObj); insertIntermediateJoinTableValue(baseObj, true); clearFKValueInAssociatedTable(baseObj); } /** * Get the id field by the passed in field list. * * @param supportedFields * The field list to find from. * @return The id field. If not found one return null. */ private Field getIdField(List supportedFields) { for (Field field : supportedFields) { if (isIdColumn(field.getName())) { return field; } } return null; } /** * If the model saved failed, throw an exception. * * @param id * The id returned by SQLite. -1 means saved failed. */ private void throwIfSaveFailed(long id) { if (id == -1) { throw new LitePalSupportException(LitePalSupportException.SAVE_FAILED); } } /** * Assign the generated id value to the model. The * {@link LitePalSupport#baseObjId} will be assigned anyway. If the model has a * field named id or _id, LitePal will assign it too. The * {@link LitePalSupport#baseObjId} will be used as identify of this model for * system use. The id or _id field will help developers for their own * purpose. * * @param baseObj * Current model that is persisted. * @param idField * The field of id. * @param id * The value of id. */ private void assignIdValue(LitePalSupport baseObj, Field idField, long id) { try { giveBaseObjIdValue(baseObj, id); if (idField != null) { giveModelIdValue(baseObj, idField.getName(), idField.getType(), id); } } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * After saving a model, the id for this model will be returned. Assign this * id to the model's id or _id field if it exists. * * @param baseObj * The class of base object. * @param idName * The name of id. Only id or _id is valid. * @param idType * The type of id. Only int or long is valid. * @param id * The value of id. */ private void giveModelIdValue(LitePalSupport baseObj, String idName, Class idType, long id) throws SecurityException, IllegalArgumentException, IllegalAccessException { if (shouldGiveModelIdValue(idName, idType, id)) { Object value; if (idType == int.class || idType == Integer.class) { value = (int) id; } else if (idType == long.class || idType == Long.class) { value = id; } else { throw new LitePalSupportException(LitePalSupportException.ID_TYPE_INVALID_EXCEPTION); } DynamicExecutor.setField(baseObj, idName, value, baseObj.getClass()); } } /** * If the table for this model have a foreign key column, the value of * foreign key id should be saved too. * * @param values * The instance of ContentValues to put foreign key value. */ private void putForeignKeyValue(ContentValues values, LitePalSupport baseObj) { Map associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK(); for (String associatedTableName : associatedModelMap.keySet()) { values.put(getForeignKeyColumnName(associatedTableName), associatedModelMap.get(associatedTableName)); } } /** * Update the foreign keys in the associated model's table. * * @param baseObj * Current model that is persisted. */ private void updateAssociatedTableWithFK(LitePalSupport baseObj) { Map> associatedModelMap = baseObj.getAssociatedModelsMapWithFK(); ContentValues values = new ContentValues(); for (String associatedTableName : associatedModelMap.keySet()) { values.clear(); String fkName = getForeignKeyColumnName(baseObj.getTableName()); values.put(fkName, baseObj.getBaseObjId()); Set ids = associatedModelMap.get(associatedTableName); if (ids != null && !ids.isEmpty()) { mDatabase.update(associatedTableName, values, getWhereOfIdsWithOr(ids), null); } } } /** * When the associations breaks between current model and associated models, * clear all the associated models' foreign key value if it exists. * * @param baseObj * Current model that is persisted. */ private void clearFKValueInAssociatedTable(LitePalSupport baseObj) { List associatedTableNames = baseObj.getListToClearAssociatedFK(); for (String associatedTableName : associatedTableNames) { String fkColumnName = getForeignKeyColumnName(baseObj.getTableName()); ContentValues values = new ContentValues(); values.putNull(fkColumnName); String whereClause = fkColumnName + " = " + baseObj.getBaseObjId(); mDatabase.update(associatedTableName, values, whereClause, null); } } /** * Insert values into intermediate join tables for self model and associated * models. * * @param baseObj * Current model that is persisted. * @param isUpdate * The current action is update or not. */ private void insertIntermediateJoinTableValue(LitePalSupport baseObj, boolean isUpdate) { Map> associatedIdsM2M = baseObj.getAssociatedModelsMapForJoinTable(); ContentValues values = new ContentValues(); for (String associatedTableName : associatedIdsM2M.keySet()) { String joinTableName = getIntermediateTableName(baseObj, associatedTableName); if (isUpdate) { mDatabase.delete(joinTableName, getWhereForJoinTableToDelete(baseObj), new String[] { String.valueOf(baseObj.getBaseObjId()) }); } List associatedIdsM2MSet = associatedIdsM2M.get(associatedTableName); if (associatedIdsM2MSet != null) { for (long associatedId : associatedIdsM2MSet) { values.clear(); values.put(getForeignKeyColumnName(baseObj.getTableName()), baseObj.getBaseObjId()); values.put(getForeignKeyColumnName(associatedTableName), associatedId); mDatabase.insert(joinTableName, null, values); } } } } /** * Get the where clause to delete intermediate join table's value for * updating. * * @param baseObj * Current model that is persisted. * @return The where clause to execute. */ private String getWhereForJoinTableToDelete(LitePalSupport baseObj) { StringBuilder where = new StringBuilder(); where.append(getForeignKeyColumnName(baseObj.getTableName())); where.append(" = ?"); return where.toString(); } /** * Judge should assign id value to model's id field. The principle is that * if id name is not null, id type is not null and id is greater than 0, * then should assign id value to it. * * @param idName * The name of id field. * @param idType * The type of id field. * @param id * The value of id. * @return If id name is not null, id type is not null and id is greater * than 0, return true. Otherwise return false. */ private boolean shouldGiveModelIdValue(String idName, Class idType, long id) { return idName != null && idType != null && id > 0; } /** * Update the generic data in generic tables. Need to delete the related generic data before * saving, because generic data has no id. * @param baseObj * Current model that is persisted. *@param supportedGenericFields * List of all supported generic fields. * @param id * The id of current model. */ private void updateGenericTables(LitePalSupport baseObj, List supportedGenericFields, long id) throws IllegalAccessException, InvocationTargetException { for (Field field : supportedGenericFields) { Encrypt annotation = field.getAnnotation(Encrypt.class); String algorithm = null; String genericTypeName = getGenericTypeName(field); if (annotation != null && "java.lang.String".equals(genericTypeName)) { algorithm = annotation.algorithm(); } field.setAccessible(true); Collection collection = (Collection) field.get(baseObj); if (collection != null) { Log.d(TAG, "updateGenericTables: class name is " + baseObj.getClassName() + " , field name is " + field.getName() ); String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); mDatabase.delete(tableName, genericValueIdColumnName + " = ?", new String[] {String.valueOf(id)}); for (Object object : collection) { ContentValues values = new ContentValues(); values.put(genericValueIdColumnName, id); object = encryptValue(algorithm, object); if (baseObj.getClassName().equals(genericTypeName)) { LitePalSupport dataSupport = (LitePalSupport) object; if (dataSupport == null) { continue; } long baseObjId = dataSupport.getBaseObjId(); if (baseObjId <= 0) { continue; } values.put(DBUtility.getM2MSelfRefColumnName(field), baseObjId); } else { Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), object }; Class[] parameterTypes = new Class[] { String.class, getGenericTypeClass(field) }; DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); } mDatabase.insert(tableName, null, values); } } } } } ================================================ FILE: core/src/main/java/org/litepal/crud/UpdateHandler.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import org.litepal.Operator; import org.litepal.annotation.Encrypt; import org.litepal.crud.model.AssociationsInfo; import org.litepal.exceptions.LitePalSupportException; import org.litepal.util.BaseUtility; import org.litepal.util.DBUtility; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import static org.litepal.util.BaseUtility.changeCase; /** * This is a component under LitePalSupport. It deals with the updating stuff as * primary task. Either updating specifying data with id or updating multiple * lines with conditions can be done here. * * @author Tony Green * @since 1.1 */ public class UpdateHandler extends DataHandler { /** * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not * allow to create instance of UpdateHandler out of CRUD package. * * @param db * The instance of SQLiteDatabase. */ public UpdateHandler(SQLiteDatabase db) { mDatabase = db; } /** * The open interface for other classes in CRUD package to update. Using * baseObj to decide which table to update, and id to decide a specific row. * The value that need to update is stored in baseObj. * * @param baseObj * Which table to update by model instance. * @param id * Which record to update. * @return The number of rows affected. * @throws java.lang.reflect.InvocationTargetException * @throws IllegalAccessException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws SecurityException */ int onUpdate(LitePalSupport baseObj, long id) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { List supportedFields = getSupportedFields(baseObj.getClassName()); List supportedGenericFields = getSupportedGenericFields(baseObj.getClassName()); updateGenericTables(baseObj, supportedGenericFields, id); ContentValues values = new ContentValues(); putFieldsValue(baseObj, supportedFields, values); putFieldsToDefaultValue(baseObj, values, id); if (values.size() > 0) { return mDatabase.update(baseObj.getTableName(), values, "id = " + id, null); } return 0; } /** * The open interface for other classes in CRUD package to update. Using * modelClass to decide which table to update, and id to decide a specific * row. The value that need to update is stored in ContentValues. * * @param modelClass * Which table to update by class. * @param id * Which record to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @return The number of rows affected. */ public int onUpdate(Class modelClass, long id, ContentValues values) { if (values.size() > 0) { convertContentValues(values); return mDatabase.update(getTableName(modelClass), values, "id = " + id, null); } return 0; } /** * The open interface for other classes in CRUD package to update multiple * rows. Using baseObj to decide which table to update, and conditions * representing the WHERE part of an SQL statement. The value that need to * update is stored in baseObj. * * @param baseObj * Which table to update by model instance. * @param conditions * A string array representing the WHERE part of an SQL * statement. * @return The number of rows affected. * @throws java.lang.reflect.InvocationTargetException * @throws IllegalAccessException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws SecurityException */ @SuppressWarnings("unchecked") int onUpdateAll(LitePalSupport baseObj, String... conditions) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } List supportedFields = getSupportedFields(baseObj.getClassName()); List supportedGenericFields = getSupportedGenericFields(baseObj.getClassName()); long[] ids = null; if (!supportedGenericFields.isEmpty()) { List list = (List) Operator.select("id").where(conditions).find(baseObj.getClass()); if (list.size() > 0) { ids = new long[list.size()]; for (int i = 0; i < ids.length; i++) { LitePalSupport dataSupport = list.get(i); ids[i] = dataSupport.getBaseObjId(); } updateGenericTables(baseObj, supportedGenericFields, ids); } } ContentValues values = new ContentValues(); putFieldsValue(baseObj, supportedFields, values); putFieldsToDefaultValue(baseObj, values, ids); return doUpdateAllAction(baseObj.getTableName(), values, conditions); } /** * The open interface for other classes in CRUD package to update multiple * rows. Using tableName to decide which table to update, and conditions * representing the WHERE part of an SQL statement. The value that need to * update is stored in ContentValues. * * @param tableName * Which table to update. * @param conditions * A string array representing the WHERE part of an SQL * statement. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @return The number of rows affected. */ public int onUpdateAll(String tableName, ContentValues values, String... conditions) { BaseUtility.checkConditionsCorrect(conditions); if (conditions != null && conditions.length > 0) { conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); } convertContentValues(values); return doUpdateAllAction(tableName, values, conditions); } /** * Do the action for updating multiple rows. It will check the validity of * conditions, then update rows in database. If the format of conditions is * invalid, throw LitePalSupportException. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @return The number of rows affected. */ private int doUpdateAllAction(String tableName, ContentValues values, String... conditions) { BaseUtility.checkConditionsCorrect(conditions); if (values.size() > 0) { return mDatabase.update(tableName, values, getWhereClause(conditions), getWhereArgs(conditions)); } return 0; } /** * Iterate all the fields that need to set to default value. If the field is * id, ignore it. Or put the default value of field into ContentValues. * * @param baseObj * Which table to update by model instance. * @param values * To store data of current model for persisting or updating. * @param ids * The id array of query result. */ private void putFieldsToDefaultValue(LitePalSupport baseObj, ContentValues values, long... ids) { String fieldName = null; try { LitePalSupport emptyModel = getEmptyModel(baseObj); Class emptyModelClass = emptyModel.getClass(); for (String name : baseObj.getFieldsToSetToDefault()) { if (!isIdColumn(name)) { fieldName = name; Field field = emptyModelClass.getDeclaredField(fieldName); if (isCollection(field.getType())) { if (ids != null && ids.length > 0) { String genericTypeName = getGenericTypeName(field); if (BaseUtility.isGenericTypeSupported(genericTypeName)) { String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); StringBuilder whereClause = new StringBuilder(); boolean needOr = false; for (long id : ids) { if (needOr) { whereClause.append(" or "); } whereClause.append(genericValueIdColumnName).append(" = ").append(id); needOr = true; } mDatabase.delete(tableName, whereClause.toString(), null); } } } else { putContentValuesForUpdate(emptyModel, field, values); } } } } catch (NoSuchFieldException e) { throw new LitePalSupportException(LitePalSupportException.noSuchFieldExceptioin( baseObj.getClassName(), fieldName), e); } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Unused currently. */ @SuppressWarnings("unused") private int doUpdateAssociations(LitePalSupport baseObj, long id, ContentValues values) { int rowsAffected = 0; analyzeAssociations(baseObj); updateSelfTableForeignKey(baseObj, values); rowsAffected += updateAssociatedTableForeignKey(baseObj, id); return rowsAffected; } /** * Analyze the associations of baseObj and store the result in it. The * associations will be used when deleting referenced data of baseObj. * Unused currently. * * @param baseObj * The record to update. */ private void analyzeAssociations(LitePalSupport baseObj) { try { Collection associationInfos = getAssociationInfo(baseObj .getClassName()); analyzeAssociatedModels(baseObj, associationInfos); } catch (Exception e) { throw new LitePalSupportException(e.getMessage(), e); } } /** * Unused currently. */ private void updateSelfTableForeignKey(LitePalSupport baseObj, ContentValues values) { Map associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK(); for (String associatedTable : associatedModelMap.keySet()) { String fkName = getForeignKeyColumnName(associatedTable); values.put(fkName, associatedModelMap.get(associatedTable)); } } /** * Unused currently. */ private int updateAssociatedTableForeignKey(LitePalSupport baseObj, long id) { Map> associatedModelMap = baseObj.getAssociatedModelsMapWithFK(); ContentValues values = new ContentValues(); for (String associatedTable : associatedModelMap.keySet()) { values.clear(); String fkName = getForeignKeyColumnName(baseObj.getTableName()); values.put(fkName, id); Set ids = associatedModelMap.get(associatedTable); if (ids != null && !ids.isEmpty()) { return mDatabase.update(associatedTable, values, getWhereOfIdsWithOr(ids), null); } } return 0; } /** * Update the generic data in generic tables. Need to delete the related generic data before * saving, because generic data has no id. If generic collection is null or empty, the operation * will be abort. Clear generic collection data while updating should use {@link LitePalSupport#setToDefault(String)} * method. * @param baseObj * Current model that is persisted. *@param supportedGenericFields * List of all supported generic fields. * @param ids * The id array of models. * @throws IllegalAccessException * @throws InvocationTargetException */ private void updateGenericTables(LitePalSupport baseObj, List supportedGenericFields, long... ids) throws IllegalAccessException, InvocationTargetException { if (ids != null && ids.length > 0) { for (Field field : supportedGenericFields) { Encrypt annotation = field.getAnnotation(Encrypt.class); String algorithm = null; String genericTypeName = getGenericTypeName(field); if (annotation != null && "java.lang.String".equals(genericTypeName)) { algorithm = annotation.algorithm(); } field.setAccessible(true); Collection collection = (Collection) field.get(baseObj); if (collection != null && !collection.isEmpty()) { String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); for (long id : ids) { mDatabase.delete(tableName, genericValueIdColumnName + " = ?", new String[] {String.valueOf(id)}); for (Object object : collection) { ContentValues values = new ContentValues(); values.put(genericValueIdColumnName, id); object = encryptValue(algorithm, object); if (baseObj.getClassName().equals(genericTypeName)) { LitePalSupport dataSupport = (LitePalSupport) object; if (dataSupport == null) { continue; } long baseObjId = dataSupport.getBaseObjId(); if (baseObjId <= 0) { continue; } values.put(DBUtility.getM2MSelfRefColumnName(field), baseObjId); } else { Object[] parameters = new Object[] { DBUtility.convertToValidColumnName(changeCase(field.getName())), object }; Class[] parameterTypes = new Class[] { String.class, getGenericTypeClass(field) }; DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); } mDatabase.insert(tableName, null, values); } } } } } } /** * The keys in ContentValues may be put as valid in Java but invalid in database. So convert * them into valid keys. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. */ private void convertContentValues(ContentValues values) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { Map valuesToConvert = new HashMap(); for (String key : values.keySet()) { if (DBUtility.isFieldNameConflictWithSQLiteKeywords(key)) { Object object = values.get(key); valuesToConvert.put(key, object); } } for (String key : valuesToConvert.keySet()) { String convertedKey = DBUtility.convertToValidColumnName(key); Object object = values.get(key); values.remove(key); if (object == null) { values.putNull(convertedKey); } else { String className = object.getClass().getName(); if ("java.lang.Byte".equals(className)) { values.put(convertedKey, (Byte) object); } else if ("[B".equals(className)) { values.put(convertedKey, (byte[]) object); } else if ("java.lang.Boolean".equals(className)) { values.put(convertedKey, (Boolean) object); } else if ("java.lang.String".equals(className)) { values.put(convertedKey, (String) object); } else if ("java.lang.Float".equals(className)) { values.put(convertedKey, (Float) object); } else if ("java.lang.Long".equals(className)) { values.put(convertedKey, (Long) object); } else if ("java.lang.Integer".equals(className)) { values.put(convertedKey, (Integer) object); } else if ("java.lang.Short".equals(className)) { values.put(convertedKey, (Short) object); } else if ("java.lang.Double".equals(className)) { values.put(convertedKey, (Double) object); } } } } } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/AsyncExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; /** * A simple async executor to run tasks in background thread. * * @author Tony Green * @since 2017/2/22 */ public abstract class AsyncExecutor { /** * Task that pending to run. */ private Runnable pendingTask; /** * Submit a task for pending executing. * @param task * The task with specific database operation. */ public void submit(Runnable task) { pendingTask = task; } /** * Run the pending task in background thread. */ void execute() { if (pendingTask != null) { new Thread(pendingTask).start(); } } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/AverageExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; import org.litepal.crud.callback.AverageCallback; /** * Executor for average query in background. * * @author Tony Green * @since 2017/2/22 */ public class AverageExecutor extends AsyncExecutor { private AverageCallback cb; /** * Register a callback listener and async task will start executing right away. * @param callback * Callback for average query in background. */ public void listen(AverageCallback callback) { cb = callback; execute(); } public AverageCallback getListener() { return cb; } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/CountExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; import org.litepal.crud.callback.CountCallback; /** * Executor for count query in background. * * @author Tony Green * @since 2017/2/22 */ public class CountExecutor extends AsyncExecutor { private CountCallback cb; /** * Register a callback listener and async task will start executing right away. * @param callback * Callback for count query in background. */ public void listen(CountCallback callback) { cb = callback; execute(); } public CountCallback getListener() { return cb; } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/FindExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; import org.litepal.crud.callback.FindCallback; /** * Executor for find record in background. * * @author Tony Green * @since 2017/2/22 */ public class FindExecutor extends AsyncExecutor { private FindCallback cb; /** * Register a callback listener and async task will start executing right away. * @param callback * Callback for find record in background. */ public void listen(FindCallback callback) { cb = callback; execute(); } public FindCallback getListener() { return cb; } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/FindMultiExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; import org.litepal.crud.callback.FindMultiCallback; /** * Executor for find multiple records in background. * * @author Tony Green * @since 2017/2/22 */ public class FindMultiExecutor extends AsyncExecutor { private FindMultiCallback cb; /** * Register a callback listener and async task will start executing right away. * @param callback * Callback for find multiple records in background. */ public void listen(FindMultiCallback callback) { cb = callback; execute(); } public FindMultiCallback getListener() { return cb; } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/SaveExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; import org.litepal.crud.callback.SaveCallback; /** * Executor for save records in background. * * @author Tony Green * @since 2017/2/22 */ public class SaveExecutor extends AsyncExecutor { private SaveCallback cb; /** * Register a callback listener and async task will start executing right away. * @param callback * Callback for save records in background. */ public void listen(SaveCallback callback) { cb = callback; execute(); } public SaveCallback getListener() { return cb; } } ================================================ FILE: core/src/main/java/org/litepal/crud/async/UpdateOrDeleteExecutor.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.async; import org.litepal.crud.callback.UpdateOrDeleteCallback; /** * Executor for update or delete records in background. * * @author Tony Green * @since 2017/2/22 */ public class UpdateOrDeleteExecutor extends AsyncExecutor { private UpdateOrDeleteCallback cb; /** * Register a callback listener and async task will start executing right away. * @param callback * Callback for update or delete records in background. */ public void listen(UpdateOrDeleteCallback callback) { cb = callback; execute(); } public UpdateOrDeleteCallback getListener() { return cb; } } ================================================ FILE: core/src/main/java/org/litepal/crud/callback/AverageCallback.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.callback; /** * Callback for average query in background. * * @author Tony Green * @since 2017/2/22 */ public interface AverageCallback { void onFinish(double average); } ================================================ FILE: core/src/main/java/org/litepal/crud/callback/CountCallback.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.callback; /** * Callback for count query in background. * * @author Tony Green * @since 2017/2/22 */ public interface CountCallback { void onFinish(int count); } ================================================ FILE: core/src/main/java/org/litepal/crud/callback/FindCallback.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.callback; /** * Callback for find record in background. * * @author Tony Green * @since 2017/2/22 */ public interface FindCallback { void onFinish(T t); } ================================================ FILE: core/src/main/java/org/litepal/crud/callback/FindMultiCallback.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.callback; import java.util.List; /** * Callback for find multiple records in background. * * @author Tony Green * @since 2017/2/22 */ public interface FindMultiCallback { void onFinish(List list); } ================================================ FILE: core/src/main/java/org/litepal/crud/callback/SaveCallback.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.callback; /** * Callback for save records in background. * * @author Tony Green * @since 2017/2/22 */ public interface SaveCallback { void onFinish(boolean success); } ================================================ FILE: core/src/main/java/org/litepal/crud/callback/UpdateOrDeleteCallback.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.callback; /** * Callback for update or delete records in background. * * @author Tony Green * @since 2017/2/22 */ public interface UpdateOrDeleteCallback { void onFinish(int rowsAffected); } ================================================ FILE: core/src/main/java/org/litepal/crud/model/AssociationsInfo.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.crud.model; import java.lang.reflect.Field; /** * This model holds necessary information when comes to analyze and handle * associated models of self model. * * @author Tony Green * @since 1.1 */ public class AssociationsInfo { /** * The class name of self class. */ private String selfClassName; /** * The class name of the class which associated with self class. */ private String associatedClassName; /** * The class which holds foreign key. */ private String classHoldsForeignKey; /** * The field of self class to declare has association with other class. */ private Field associateOtherModelFromSelf; /** * The field of the associated class to declare has association with self * class. */ private Field associateSelfFromOtherModel; /** * The association type, including Many2One One2One Many2Many. */ private int associationType; /** * Get the class name of self class. * * @return The self class name. */ public String getSelfClassName() { return selfClassName; } /** * Set the class name of self class. * * @param selfClassName * The self class name to set. */ public void setSelfClassName(String selfClassName) { this.selfClassName = selfClassName; } /** * Get the class name of the class which associated with self class. * * @return The associated class name. */ public String getAssociatedClassName() { return associatedClassName; } /** * Set the class name of the class which associated with self class. * * @param associatedClassName * The associated class name to set. */ public void setAssociatedClassName(String associatedClassName) { this.associatedClassName = associatedClassName; } /** * Get the class which holds foreign key. * * @return The class which holds foreign key. */ public String getClassHoldsForeignKey() { return classHoldsForeignKey; } /** * Set the class which holds foreign key. * * @param classHoldsForeignKey * The class which holds foreign key to set. */ public void setClassHoldsForeignKey(String classHoldsForeignKey) { this.classHoldsForeignKey = classHoldsForeignKey; } /** * Get the field of self class which declares has association with other * class. * * @return The field which declares has association with other class. */ public Field getAssociateOtherModelFromSelf() { return associateOtherModelFromSelf; } /** * Set the field of self class which declares has association with other * class. * * @param associateOtherModelFromSelf * The field which declares has association with other class to * set. */ public void setAssociateOtherModelFromSelf(Field associateOtherModelFromSelf) { this.associateOtherModelFromSelf = associateOtherModelFromSelf; } /** * Get the field of the associated class which declares has association with * self class. * * @return The field of the associated class which declares has association * with self class. */ public Field getAssociateSelfFromOtherModel() { return associateSelfFromOtherModel; } /** * Set the field of the associated class which declares has association with * self class. * * @param associateSelfFromOtherModel * The field of the associated class which declares has * association with self class to set. */ public void setAssociateSelfFromOtherModel(Field associateSelfFromOtherModel) { this.associateSelfFromOtherModel = associateSelfFromOtherModel; } /** * Get the association type. * * @return The association type. */ public int getAssociationType() { return associationType; } /** * Set the association type. * * @param associationType * Within ONE_TO_ONE, MANY_TO_ONE and MANY_TO_MANY constants. */ public void setAssociationType(int associationType) { this.associationType = associationType; } /** * Override equals method to make sure that if two associated classes in the * association info model are same ignoring sides, they are same association * info model. */ @Override public boolean equals(Object o) { if (o instanceof AssociationsInfo) { AssociationsInfo other = (AssociationsInfo) o; if (o != null && other != null) { if (other.getAssociationType() == associationType && other.getClassHoldsForeignKey().equals(classHoldsForeignKey)) { if (other.getSelfClassName().equals(selfClassName) && other.getAssociatedClassName().equals(associatedClassName)) { return true; } if (other.getSelfClassName().equals(associatedClassName) && other.getAssociatedClassName().equals(selfClassName)) { return true; } } } } return false; } } ================================================ FILE: core/src/main/java/org/litepal/exceptions/DataSupportException.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.exceptions; /** * When LitePal deals with CRUD actions of LitePalSupport, it may throw * DataSupportException for older versions API. The new CRUD APIs should throw * {@link LitePalSupportException} * * @author Tony Green * @since 1.1 */ public class DataSupportException extends RuntimeException { private static final long serialVersionUID = 1L; /** * Constructor of LitePalSupportException. * * @param errorMessage * the description of this exception. */ public DataSupportException(String errorMessage) { super(errorMessage); } /** * Constructor of LitePalSupportException. * * @param errorMessage * the description of this exception. * @param throwable * the cause of this exception. */ public DataSupportException(String errorMessage, Throwable throwable) { super(errorMessage, throwable); } } ================================================ FILE: core/src/main/java/org/litepal/exceptions/DatabaseGenerateException.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.exceptions; /** * When LitePal generate or update tables, it may throw DatabaseGenerateException. * * @author Tony Green * @since 1.0 */ public class DatabaseGenerateException extends RuntimeException { private static final long serialVersionUID = 1L; /** * Can not find a class with the passing class name. */ public static final String CLASS_NOT_FOUND = "can not find a class named "; /** * An exception that indicates there was an error with SQL parsing or * execution. */ public static final String SQL_ERROR = "An exception that indicates there was an error with SQL parsing or execution. "; /** * SQL syntax error when executing generation job. */ public static final String SQL_SYNTAX_ERROR = "SQL syntax error happens while executing "; /** * Can not find a table with the passing table name when executing SQL. */ public static final String TABLE_DOES_NOT_EXIST_WHEN_EXECUTING = "Table doesn't exist when executing "; /** * Can not find a table with the passing table name. */ public static final String TABLE_DOES_NOT_EXIST = "Table doesn't exist with the name of "; /** * Don't have permission to create database on sdcard. */ public static final String EXTERNAL_STORAGE_PERMISSION_DENIED = "You don't have permission to access database at %1$s. Make sure you handled WRITE_EXTERNAL_STORAGE runtime permission correctly."; /** * Constructor of DatabaseGenerateException. * * @param errorMessage * the description of this DatabaseGenerateException. */ public DatabaseGenerateException(String errorMessage) { super(errorMessage); } } ================================================ FILE: core/src/main/java/org/litepal/exceptions/GlobalException.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.exceptions; /** * This is where all the global exceptions declared of LitePal. * * @author Tony Green * @since 1.0 */ public class GlobalException extends RuntimeException { private static final long serialVersionUID = 1L; /** * Application context is null. */ public static final String APPLICATION_CONTEXT_IS_NULL = "Application context is null. Maybe you neither configured your application name with \"org.litepal.LitePalApplication\" in your AndroidManifest.xml, nor called LitePal.initialize(Context) method."; /** * Constructor of GlobalException. * * @param errorMessage * the description of this exception. */ public GlobalException(String errorMessage) { super(errorMessage); } } ================================================ FILE: core/src/main/java/org/litepal/exceptions/InvalidAttributesException.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.exceptions; /** * Reading the attributes in the litepal.xml file. Check all the attributes if they * are valid value. If anyone of them is not under rules, throw * InvalidAttributesException exception. * * @author Tony Green * @since 1.0 */ public class InvalidAttributesException extends RuntimeException { private static final long serialVersionUID = 1L; /** * dbname is empty or not defined in litepal.xml file. */ public static final String DBNAME_IS_EMPTY_OR_NOT_DEFINED = "dbname is empty or not defined in litepal.xml file, or your litepal.xml file is missing."; /** * the version of database can not be less than 1. */ public static final String VERSION_OF_DATABASE_LESS_THAN_ONE = "the version of database can not be less than 1"; /** * the version in litepal.xml is earlier than the current version. */ public static final String VERSION_IS_EARLIER_THAN_CURRENT = "the version in litepal.xml is earlier than the current version"; /** * There's an invalid value in cases mark. Only keep, upper, lower allowed. */ public static final String CASES_VALUE_IS_INVALID = " is an invalid value for "; /** * Constructor of InvalidAttributesException. * * @param errorMessage * the description of this InvalidAttributesException. */ public InvalidAttributesException(String errorMessage) { super(errorMessage); } } ================================================ FILE: core/src/main/java/org/litepal/exceptions/LitePalSupportException.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.exceptions; /** * When LitePal deals with CRUD actions of LitePalSupport, it may throw * LitePalSupportException. * * @author Tony Green * @since 2.0 */ public class LitePalSupportException extends DataSupportException { private static final long serialVersionUID = 1L; /** * Thrown when models have invalid type for id fields. Only int or long is * supported. */ public static final String ID_TYPE_INVALID_EXCEPTION = "id type is not supported. Only int or long is acceptable for id"; /** * Thrown when the saving model is not an instance of LitePalSupport. */ public static final String MODEL_IS_NOT_AN_INSTANCE_OF_LITE_PAL_SUPPORT = " should be inherited from LitePalSupport"; /** * Thrown when developers use wrong field to declare many2one or many2many * associations. */ public static final String WRONG_FIELD_TYPE_FOR_ASSOCIATIONS = "The field to declare many2one or many2many associations should be List or Set."; /** * Thrown when fail to save a model. */ public static final String SAVE_FAILED = "Save current model failed."; /** * Thrown when there is no default constructor in model class to update. */ public static final String INSTANTIATION_EXCEPTION = " needs a default constructor."; /** * Thrown when the parameters in conditions are incorrect. */ public static final String UPDATE_CONDITIONS_EXCEPTION = "The parameters in conditions are incorrect."; /** * Constructor of LitePalSupportException. * * @param errorMessage * the description of this exception. */ public LitePalSupportException(String errorMessage) { super(errorMessage); } /** * Constructor of LitePalSupportException. * * @param errorMessage * the description of this exception. * @param throwable * the cause of this exception. */ public LitePalSupportException(String errorMessage, Throwable throwable) { super(errorMessage, throwable); } /** * Thrown when the VM notices that a program tries to reference, on a class * or object, a method that does not exist. * * @param className * The class name. * @param methodName * The method name which is missing. * @return Exception message. */ public static String noSuchMethodException(String className, String methodName) { return "The " + methodName + " method in " + className + " class is necessary which does not exist."; } /** * Thrown when the virtual machine notices that a program tries to * reference, on a class or object, a field that does not exist. * * @param className * The class name. * @param fieldName * The field name which is missing. * @return Exception message. */ public static String noSuchFieldExceptioin(String className, String fieldName) { return "The " + fieldName + " field in " + className + " class is necessary which does not exist."; } } ================================================ FILE: core/src/main/java/org/litepal/exceptions/ParseConfigurationFileException.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.exceptions; /** * Using SAX way parsing XML by default. If any problem happens when parsing the * litepal.xml file, ParseConfigurationFileException will be thrown. * * @author Tony Green * @since 1.0 */ public class ParseConfigurationFileException extends RuntimeException { private static final long serialVersionUID = 1L; /** * can not find the litepal.xml file by the given id. */ public static final String CAN_NOT_FIND_LITEPAL_FILE = "litepal.xml file is missing. Please ensure it under assets folder."; /** * can not parse the litepal.xml, check if it's in correct format. */ public static final String FILE_FORMAT_IS_NOT_CORRECT = "can not parse the litepal.xml, check if it's in correct format"; /** * parse configuration is failed. */ public static final String PARSE_CONFIG_FAILED = "parse configuration is failed"; /** * IO exception happened. */ public static final String IO_EXCEPTION = "IO exception happened"; /** * Constructor of ParseConfigurationFileException. * * @param errorMessage * the description of this exception. */ public ParseConfigurationFileException(String errorMessage) { super(errorMessage); } } ================================================ FILE: core/src/main/java/org/litepal/extension/FluentQuery.kt ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.extension import org.litepal.FluentQuery import org.litepal.crud.async.FindExecutor /** * Extension of FluentQuery class for Kotlin api. * @author Tony Green * @since 2.1 */ /** * Finds multiple records by the cluster parameters. You can use the below * way to finish a complicated query: * ``` * LitePal.select("name").where("age > ?", "14").order("age").limit(1).offset(2).find() * ``` * You can also do the same job with SQLiteDatabase like this: * ``` * getSQLiteDatabase().query("Person", "name", "age > ?", new String[] { "14" }, null, null, "age", * "2,1") * ``` * Obviously, the first way is much more semantic.
* Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#find(Class, boolean)}. * * @return An object list with founded data from database, or an empty list. */ inline fun FluentQuery.find(): List = find(T::class.java) /** * Basically same as {@link #find(Class)} but pending to a new thread for executing. * * @return A FindMultiExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun FluentQuery.findAsync() = findAsync(T::class.java) /** * It is mostly same as [FluentQuery.find(Class)] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object list with founded data from database, or an empty list. */ inline fun FluentQuery.find(isEager: Boolean): List = find(T::class.java, isEager) /** * Basically same as {@link #find(Class, boolean)} but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @return A FindMultiExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun FluentQuery.findAsync(isEager: Boolean) = findAsync(T::class.java, isEager) /** * Finds the first record by the cluster parameters. You can use the below * way to finish a complicated query: * ``` * LitePal.select("name").where("age > ?", "14").order("age").limit(10).offset(2).findFirst() * ``` * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [FluentQuery.findFirst(Class, boolean)]. * * @return An object with founded data from database, or null. */ inline fun FluentQuery.findFirst(): T? = findFirst(T::class.java) /** * Basically same as {@link #findFirst(Class)} but pending to a new thread for executing. * * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun FluentQuery.findFirstAsync(): FindExecutor = findFirstAsync(T::class.java) /** * It is mostly same as [FluentQuery.findFirst(Class)] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with founded data from database, or null. */ inline fun FluentQuery.findFirst(isEager: Boolean): T? = findFirst(T::class.java, isEager) /** * Finds the last record by the cluster parameters. You can use the below * way to finish a complicated query: * ``` * LitePal.select("name").where("age > ?", "14").order("age").limit(10).offset(2).findLast() * ``` * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [FluentQuery.findLast(Class, boolean)]. * * @return An object with founded data from database, or null. */ inline fun FluentQuery.findLast(): T? = findLast(T::class.java) /** * It is mostly same as [FluentQuery.findLast(Class)] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with founded data from database, or null. */ inline fun FluentQuery.findLast(isEager: Boolean): T? = findLast(T::class.java, isEager) /** * Count the records. * ``` * LitePal.count() * ``` * This will count all rows in person table. * * You can also specify a where clause when counting. * ``` * LitePal.where("age > ?", "15").count() * ``` * @return Count of the specified table. */ inline fun FluentQuery.count() = count(T::class.java) /** * Calculates the average value on a given column. * ``` * LitePal.average("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").average("age") * ``` * @param column * The based on column to calculate. * @return The average value on a given column. */ inline fun FluentQuery.average(column: String) = average(T::class.java, column) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.max("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").max("age") * ``` * @param columnName * The based on column to calculate. * * @return The maximum value on a given column. */ inline fun FluentQuery.max(columnName: String): R = max(T::class.java, columnName, R::class.java) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.max("person", "age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").max("person", "age") * ``` * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The maximum value on a given column. */ inline fun FluentQuery.max(tableName: String, columnName: String): R = max(tableName, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.min("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").min("age") * ``` * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun FluentQuery.min(columnName: String): R = min(T::class.java, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.min("person", "age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").min("person", "age") * ``` * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun FluentQuery.min(tableName: String, columnName: String): R = min(tableName, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.sum("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").sum("age") * ``` * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun FluentQuery.sum(columnName: String): R = sum(T::class.java, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.sum("person", "age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").sum("person", "age") * ``` * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun FluentQuery.sum(tableName: String, columnName: String): R = sum(tableName, columnName, R::class.java) ================================================ FILE: core/src/main/java/org/litepal/extension/LitePal.kt ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.extension import android.content.ContentValues import org.litepal.LitePal import org.litepal.crud.LitePalSupport import java.lang.Exception /** * Extension of LitePal class for Kotlin api. * @author Tony Green * @since 2.1 */ /** * Count the records. * ``` * LitePal.count() * ``` * This will count all rows in person table. * * You can also specify a where clause when counting. * ``` * LitePal.where("age > ?", "15").count() * ``` * @return Count of the specified table. */ inline fun LitePal.count() = count(T::class.java) /** * Basically same as [LitePal.count] but pending to a new thread for executing. * * @return A CountExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.countAsync() = countAsync(T::class.java) /** * Calculates the average value on a given column. * ``` * LitePal.average("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").average("age") * ``` * @param column * The based on column to calculate. * @return The average value on a given column. */ inline fun LitePal.average(column: String) = average(T::class.java, column) /** * Basically same as [LitePal.average] but pending to a new thread for executing. * * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.averageAsync(column: String) = averageAsync(T::class.java, column) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.max("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").max("age") * ``` * @param columnName * The based on column to calculate. * * @return The maximum value on a given column. */ inline fun LitePal.max(columnName: String) = max(T::class.java, columnName, R::class.java) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.maxAsync(columnName: String) = maxAsync(T::class.java, columnName, R::class.java) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.max("person", "age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").max("person", "age") * ``` * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The maximum value on a given column. */ inline fun LitePal.max(tableName: String, columnName: String) = max(tableName, columnName, R::class.java) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.maxAsync(tableName: String, columnName: String) = maxAsync(tableName, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.min("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").min("age") * ``` * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun LitePal.min(columnName: String) = min(T::class.java, columnName, R::class.java) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.minAsync(columnName: String) = minAsync(T::class.java, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.min("person", "age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").min("person", "age") * ``` * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun LitePal.min(tableName: String, columnName: String) = min(tableName, columnName, R::class.java) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.minAsync(tableName: String, columnName: String) = minAsync(tableName, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.sum("age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").sum("age") * ``` * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun LitePal.sum(columnName: String) = sum(T::class.java, columnName, R::class.java) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.sumAsync(columnName: String) = sumAsync(T::class.java, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * ``` * LitePal.sum("person", "age") * ``` * You can also specify a where clause when calculating. * ``` * LitePal.where("age > ?", "15").sum("person", "age") * ``` * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun LitePal.sum(tableName: String, columnName: String) = sum(tableName, columnName, R::class.java) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.sumAsync(tableName: String, columnName: String) = sumAsync(tableName, columnName, R::class.java) /** * Finds the record by a specific id. * ``` * val person = LitePal.find(1) * ``` * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using[LitePal.find] with isEager parameter. * * @param id * Which record to query. * @return An object with found data from database, or null. */ inline fun LitePal.find(id: Long): T? = find(T::class.java, id) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param id * Which record to query. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findAsync(id: Long) = findAsync(T::class.java, id) /** * It is mostly same as [LitePal.find] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ inline fun LitePal.find(id: Long, isEager: Boolean) = find(T::class.java, id, isEager) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findAsync(id: Long, isEager: Boolean) = find(T::class.java, id, isEager) /** * Finds the first record of a single table. * ``` * val person = LitePal.findFirst() * ``` * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findFirst] with isEager parameter. * * @return An object with data of first row, or null. */ inline fun LitePal.findFirst() = findFirst(T::class.java) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findFirstAsync() = findFirstAsync(T::class.java) /** * It is mostly same as [LitePal.findFirst] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ inline fun LitePal.findFirst(isEager: Boolean) = findFirst(T::class.java, isEager) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findFirstAsync(isEager: Boolean) = findFirstAsync(T::class.java, isEager) /** * Finds the last record of a single table. * ``` * val p = LitePal.findLast() * ``` * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findLast] with isEager parameter. * * @return An object with data of last row, or null. */ inline fun LitePal.findLast() = findLast(T::class.java) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findLastAsync() = findLastAsync(T::class.java) /** * It is mostly same as [LitePal.findLast] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ inline fun LitePal.findLast(isEager: Boolean) = findLast(T::class.java, isEager) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findLastAsync(isEager: Boolean) = findLastAsync(T::class.java, isEager) /** * Finds multiple records by an id array. * ``` * val people = LitePal.findAll(1, 2, 3) * val bookIds = longArrayOf(10, 18) * LitePal.findAll(*bookIds) * ``` * Of course you can find all records by passing nothing to the ids * parameter. * * val allBooks = LitePal.findAll() * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findAll] with isEager parameter. * * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ inline fun LitePal.findAll(vararg ids: Long) = findAll(T::class.java, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findAllAsync(vararg ids: Long) = findAllAsync(T::class.java, *ids) /** * It is mostly same as [LitePal.findAll] but an * isEager parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ inline fun LitePal.findAll(isEager: Boolean, vararg ids: Long) = findAll(T::class.java, isEager, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.findAllAsync(isEager: Boolean, vararg ids: Long) = findAllAsync(T::class.java, isEager, *ids) /** * Deletes the record in the database by id. * * The data in other tables which is referenced with the record will be * removed too. * ``` * LitePal.delete(1) * ``` * This means that the record 1 in person table will be removed. * * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ inline fun LitePal.delete(id: Long) = delete(T::class.java, id) /** * Basically same as [LitePal.delete] but pending to a new thread for executing. * * @param id * Which record to delete. * @return A UpdateOrDeleteExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.deleteAsync(id: Long) = deleteAsync(T::class.java, id) /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * ``` * LitePal.deleteAll("name = ? and age = ?", "Tom", "14") * ``` * This means that all the records which name is Tom and age is 14 will be * removed. * * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return The number of rows affected. */ inline fun LitePal.deleteAll(vararg conditions: String?) = deleteAll(T::class.java, *conditions) /** * Basically same as [LitePal.deleteAll] but pending to a new thread for executing. * * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return A UpdateOrDeleteExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.deleteAllAsync(vararg conditions: String?) = deleteAllAsync(T::class.java, *conditions) /** * Updates the corresponding record by id with ContentValues. Returns the * number of affected rows. * ``` * val cv = ContentValues() * cv.put("name", "Jim") * LitePal.update(cv, 1) * ``` * This means that the name of record 1 will be updated into Jim. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return The number of rows affected. */ inline fun LitePal.update(values: ContentValues, id: Long) = update(T::class.java, values, id) /** * Basically same as [LitePal.update] but pending to a new thread for executing. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return A UpdateOrDeleteExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.updateAsync(values: ContentValues, id: Long) = updateAsync(T::class.java, values, id) /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * ``` * val cv = ContentValues() * cv.put("name", "Jim") * LitePal.update(cv, "name = ?", "Tom") * ``` * This means that all the records which name is Tom will be updated into * Jim. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ inline fun LitePal.updateAll(values: ContentValues, vararg conditions: String?) = updateAll(T::class.java, values, *conditions) /** * Basically same as [LitePal.updateAll] but pending to a new thread for executing. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ @Deprecated("This method is deprecated and will be removed in the future releases.", ReplaceWith("Handle async db operation in your own logic instead.")) inline fun LitePal.updateAllAsync(values: ContentValues, vararg conditions: String?) = updateAllAsync(T::class.java, values, *conditions) /** * Check if the specified conditions data already exists in the table. * @param conditions * A filter declaring which data to check. Exactly same use as * [LitePal.where], except null conditions will result in false. * @return Return true if the specified conditions data already exists in the table. * False otherwise. Null conditions will result in false. */ inline fun LitePal.isExist(vararg conditions: String?) = isExist(T::class.java, *conditions) /** * Saves the collection into database. * ``` * val people = listOf(...) * people.saveAll(); * ``` * If the model in collection is a new record gets created in the database, * otherwise the existing record gets updated. * * If saving process failed by any accident, the whole action will be * cancelled and your database will be **rolled back**. * * This method acts the same result as the below way, but **much more * efficient**. * ``` * for (person in people) { * person.save() * } * ``` * * @param collection * Holds all models to save. * @return True if all records in collection are saved. False none record in collection is saved. There won't be partial saved condition. */ fun Collection.saveAll() = LitePal.saveAll(this) /** * Open a transaction scope, all codes in the lambda will under transaction. * If lambda return true, all db operations in lambda will be committed. * Otherwise all db operations will be rolled back. */ @Synchronized fun LitePal.runInTransaction(block: () -> Boolean): Boolean { beginTransaction() val succeeded = try { block() } catch (e: Exception) { false } if (succeeded) { setTransactionSuccessful() } endTransaction() return succeeded } ================================================ FILE: core/src/main/java/org/litepal/model/Table_Schema.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.model; /** * This class is a constant model class. It stores each table name of the * corresponding model classes added by developers. When synchronizing the * tables with the model classes, the table names are necessary to decide which * tables are created by the developers and which are created by system. The * values in table_schema are totally generated automatically, do not try to * change any value in it or the synchronization might be failed. * * @author Tony Green * @since 1.0 */ public class Table_Schema { /** * The table name of model class. */ private String name; /** * Type of the table. 0 normal table, 1 intermediate join table. */ private int type; /** * Get the table name. * * @return The table name. */ public String getName() { return name; } /** * Set the table name. * * @param name * The table name. */ public void setName(String name) { this.name = name; } /** * Get the table type. * * @return The table type. */ public int getType() { return type; } /** * Set the table type. * * @param type * The table type to set */ public void setType(int type) { this.type = type; } } ================================================ FILE: core/src/main/java/org/litepal/parser/LitePalAttr.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.parser; import java.util.ArrayList; import java.util.List; import org.litepal.exceptions.InvalidAttributesException; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.SharedUtil; import android.text.TextUtils; /** * The object model for the litepal.xml file. Once database connection happens, * LitePal will try to analysis the litepal.xml, and read all the attribute into * the LitePalAttr model for further usage. * * @author Tony Green * @since 1.0 */ public final class LitePalAttr { /** * Static litePalAttr object. */ private static LitePalAttr litePalAttr; /** * The version of database. */ private int version; /** * The name of database. */ private String dbName; /** * The case of table names and column names and SQL. */ private String cases; /** * Define where the .db file should be. Option values: internal, external, or path in sdcard. */ private String storage; /** * All the model classes that want to map in the database. Each class should * be given the full name including package name. */ private List classNames; /** * Extra name as key for saving the database version in SharedUtil. */ private String extraKeyName; /** * Do not allow new a LitePalAttr object. Makes it a singleton class. */ private LitePalAttr() { } /** * Provide a way to get the instance of LitePalAttr. * @return the singleton instance of LitePalAttr */ public static LitePalAttr getInstance() { if (litePalAttr == null) { synchronized (LitePalAttr.class) { if (litePalAttr == null) { litePalAttr = new LitePalAttr(); loadLitePalXMLConfiguration(); } } } return litePalAttr; } private static void loadLitePalXMLConfiguration() { if (BaseUtility.isLitePalXMLExists()) { LitePalConfig config = LitePalParser.parseLitePalConfiguration(); litePalAttr.setDbName(config.getDbName()); litePalAttr.setVersion(config.getVersion()); litePalAttr.setClassNames(config.getClassNames()); litePalAttr.setCases(config.getCases()); litePalAttr.setStorage(config.getStorage()); } } /** * Clear the instance of LitePalAttr. */ public static void clearInstance() { litePalAttr = null; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public String getDbName() { return dbName; } public void setDbName(String dbName) { this.dbName = dbName; } public String getStorage() { return storage; } public void setStorage(String storage) { this.storage = storage; } public String getExtraKeyName() { return extraKeyName; } public void setExtraKeyName(String extraKeyName) { this.extraKeyName = extraKeyName; } /** * Get the class name list. Always add table_schema as a value. * * @return The class name list. */ public List getClassNames() { if (classNames == null) { classNames = new ArrayList(); classNames.add("org.litepal.model.Table_Schema"); } else if (classNames.isEmpty()) { classNames.add("org.litepal.model.Table_Schema"); } return classNames; } /** * Add a class name into the current mapping model list. * * @param className * Full package class name. */ public void addClassName(String className) { getClassNames().add(className); } public void setClassNames(List classNames) { this.classNames = classNames; } public String getCases() { return cases; } public void setCases(String cases) { this.cases = cases; } /** * Before application build the connection with database, check the fields * in LitePalAttr. If all of the fields are passed, the connection will be * continued.If anyone of them doesn't pass, an exception will be thrown. * If dbname is undefined, or version is less than 1, or version is earlier * than current version, throw InvalidAttributesException. * * @throws org.litepal.exceptions.InvalidAttributesException */ public void checkSelfValid() { if (TextUtils.isEmpty(dbName)) { loadLitePalXMLConfiguration(); if (TextUtils.isEmpty(dbName)) { throw new InvalidAttributesException( InvalidAttributesException.DBNAME_IS_EMPTY_OR_NOT_DEFINED); } } if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { dbName = dbName + Const.Config.DB_NAME_SUFFIX; } if (version < 1) { throw new InvalidAttributesException( InvalidAttributesException.VERSION_OF_DATABASE_LESS_THAN_ONE); } if (version < SharedUtil.getLastVersion(extraKeyName)) { throw new InvalidAttributesException( InvalidAttributesException.VERSION_IS_EARLIER_THAN_CURRENT); } if (TextUtils.isEmpty(cases)) { cases = Const.Config.CASES_LOWER; } else { if (!cases.equals(Const.Config.CASES_UPPER) && !cases.equals(Const.Config.CASES_LOWER) && !cases.equals(Const.Config.CASES_KEEP)) { throw new InvalidAttributesException(cases + InvalidAttributesException.CASES_VALUE_IS_INVALID); } } } } ================================================ FILE: core/src/main/java/org/litepal/parser/LitePalConfig.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.parser; import java.util.ArrayList; import java.util.List; /** * Model for litepal.xml configuration file. * @author guolin * @since 2016/11/10 */ public class LitePalConfig { /** * The version of database. */ private int version; /** * The name of database. */ private String dbName; /** * The case of table names and column names and SQL. */ private String cases; /** * Define where the .db file should be. Option values: internal external. */ private String storage; /** * All the model classes that want to map in the database. Each class should * be given the full name including package name. */ private List classNames; public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public String getDbName() { return dbName; } public void setDbName(String dbName) { this.dbName = dbName; } public String getStorage() { return storage; } public void setStorage(String storage) { this.storage = storage; } /** * Get the class name list. Always add table_schema as a value. * * @return The class name list. */ public List getClassNames() { if (classNames == null) { classNames = new ArrayList(); classNames.add("org.litepal.model.Table_Schema"); } else if (classNames.isEmpty()) { classNames.add("org.litepal.model.Table_Schema"); } return classNames; } /** * Add a class name into the current mapping model list. * * @param className * Full package class name. */ public void addClassName(String className) { getClassNames().add(className); } public void setClassNames(List classNames) { this.classNames = classNames; } public String getCases() { return cases; } public void setCases(String cases) { this.cases = cases; } } ================================================ FILE: core/src/main/java/org/litepal/parser/LitePalContentHandler.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.parser; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * This is the content handler for analysis the litepal.xml file by SAXParser, * and temporarily the only correct way to generate LitePalAttr model with * values. * * @author Tony Green * @since 1.0 */ public class LitePalContentHandler extends DefaultHandler { /** * Store the parsed value of litepal.xml. */ private LitePalAttr litePalAttr; /** * Characters in the characters tag. Decide to not use this method * temporarily. Use value attribute instead. */ @Override public void characters(char[] ch, int start, int length) throws SAXException { } /** * End of the document. Doing nothing temporarily. */ @Override public void endDocument() throws SAXException { } /** * End of the element. Doing nothing temporarily. */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { } /** * Start of the document. Generate a LitePalAttr model at the same time. */ @Override public void startDocument() throws SAXException { litePalAttr = LitePalAttr.getInstance(); litePalAttr.getClassNames().clear(); } /** * Start analysis the litepal.xml file. Set all the parsed value into the * LitePalAttr model. */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (LitePalParser.NODE_DB_NAME.equalsIgnoreCase(localName)) { for (int i = 0; i < attributes.getLength(); i++) { if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { litePalAttr.setDbName(attributes.getValue(i).trim()); } } } else if (LitePalParser.NODE_VERSION.equalsIgnoreCase(localName)) { for (int i = 0; i < attributes.getLength(); i++) { if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { litePalAttr.setVersion(Integer.parseInt(attributes.getValue(i).trim())); } } } else if (LitePalParser.NODE_MAPPING.equalsIgnoreCase(localName)) { for (int i = 0; i < attributes.getLength(); i++) { if (LitePalParser.ATTR_CLASS.equalsIgnoreCase(attributes.getLocalName(i))) { litePalAttr.addClassName(attributes.getValue(i).trim()); } } } else if (LitePalParser.NODE_CASES.equalsIgnoreCase(localName)) { for (int i = 0; i < attributes.getLength(); i++) { if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { litePalAttr.setCases(attributes.getValue(i).trim()); } } } else if (LitePalParser.NODE_STORAGE.equalsIgnoreCase(localName)) { for (int i = 0; i < attributes.getLength(); i++) { if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { litePalAttr.setStorage(attributes.getValue(i).trim()); } } } } } ================================================ FILE: core/src/main/java/org/litepal/parser/LitePalParser.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.parser; import java.io.IOException; import java.io.InputStream; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import org.litepal.LitePalApplication; import org.litepal.exceptions.ParseConfigurationFileException; import org.litepal.util.Const; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import android.content.res.AssetManager; import android.content.res.Resources.NotFoundException; /** * The class is used to parse the litepal.xml file. There're three usual ways to * parse XML in android, SAX, Pull and DOM. LitePal use SAX as default option, * and DOM parser will be added soon. * * @author Tony Green * @since 1.0 */ public class LitePalParser { /** * Node name dbname. */ static final String NODE_DB_NAME = "dbname"; /** * Node name version. */ static final String NODE_VERSION = "version"; /** * Node name list. Currently not used. */ static final String NODE_LIST = "list"; /** * Node name mapping. */ static final String NODE_MAPPING = "mapping"; /** * Node name column case. */ static final String NODE_CASES = "cases"; /** * Node name column storage. */ static final String NODE_STORAGE = "storage"; /** * Attribute name value, for dbname and version node. */ static final String ATTR_VALUE = "value"; /** * Attribute name class, for mapping node. */ static final String ATTR_CLASS = "class"; /** * Store the parsed value of litepal.xml. */ private static LitePalParser parser; /** * Analyze litepal.xml, and store the analyzed result in LitePalParser. Use * DomParse to parse the configuration file as default. SAXParser and * XmlPullParser is also optional, but not visible to developers. */ public static LitePalConfig parseLitePalConfiguration() { if (parser == null) { parser = new LitePalParser(); } return parser.usePullParse(); } /** * Use SAXParser to parse the litepal.xml file. It will get the parsed * result from LitePalContentHandler and stored in the instance of * LitePalAttr. * * Note while analyzing litepal.xml file, ParseConfigurationFileException * could be thrown. Be careful of writing litepal.xml file, or developer's * application may be crash. */ private void useSAXParser() { LitePalContentHandler handler; try { SAXParserFactory factory = SAXParserFactory.newInstance(); XMLReader xmlReader = factory.newSAXParser().getXMLReader(); handler = new LitePalContentHandler(); xmlReader.setContentHandler(handler); xmlReader.parse(new InputSource(getConfigInputStream())); } catch (NotFoundException e) { throw new ParseConfigurationFileException( ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE); } catch (SAXException e) { throw new ParseConfigurationFileException( ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT); } catch (ParserConfigurationException e) { throw new ParseConfigurationFileException( ParseConfigurationFileException.PARSE_CONFIG_FAILED); } catch (IOException e) { throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION); } } /** * Use XmlPullParser to parse the litepal.xml file. It will store the result * in the instance of LitePalAttr. * * Note while analyzing litepal.xml file, ParseConfigurationFileException * could be thrown. Be careful of writing litepal.xml file, or developer's * application may be crash. */ private LitePalConfig usePullParse() { try { LitePalConfig litePalConfig = new LitePalConfig(); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = factory.newPullParser(); xmlPullParser.setInput(getConfigInputStream(), "UTF-8"); int eventType = xmlPullParser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { String nodeName = xmlPullParser.getName(); switch (eventType) { case XmlPullParser.START_TAG: { if (NODE_DB_NAME.equals(nodeName)) { String dbName = xmlPullParser.getAttributeValue("", ATTR_VALUE); litePalConfig.setDbName(dbName); } else if (NODE_VERSION.equals(nodeName)) { String version = xmlPullParser.getAttributeValue("", ATTR_VALUE); litePalConfig.setVersion(Integer.parseInt(version)); } else if (NODE_MAPPING.equals(nodeName)) { String className = xmlPullParser.getAttributeValue("", ATTR_CLASS); litePalConfig.addClassName(className); } else if (NODE_CASES.equals(nodeName)) { String cases = xmlPullParser.getAttributeValue("", ATTR_VALUE); litePalConfig.setCases(cases); } else if (NODE_STORAGE.equals(nodeName)) { String storage = xmlPullParser.getAttributeValue("", ATTR_VALUE); litePalConfig.setStorage(storage); } break; } default: break; } eventType = xmlPullParser.next(); } return litePalConfig; } catch (XmlPullParserException e) { throw new ParseConfigurationFileException( ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT); } catch (IOException e) { throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION); } } /** * Iterates all files in the root of assets folder. If find litepal.xml, * open this file and return the input stream. Or throw * ParseConfigurationFileException. * * @return The input stream of litepal.xml. * @throws java.io.IOException */ private InputStream getConfigInputStream() throws IOException { AssetManager assetManager = LitePalApplication.getContext().getAssets(); String[] fileNames = assetManager.list(""); if (fileNames != null && fileNames.length > 0) { for (String fileName : fileNames) { if (Const.Config.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) { return assetManager.open(fileName, AssetManager.ACCESS_BUFFER); } } } throw new ParseConfigurationFileException( ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE); } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/AssociationCreator.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import org.litepal.exceptions.DatabaseGenerateException; import org.litepal.tablemanager.model.AssociationsModel; import org.litepal.tablemanager.model.ColumnModel; import org.litepal.tablemanager.model.GenericModel; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.DBUtility; import org.litepal.util.LitePalLog; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; /** * When models have associations such as one2one, many2one or many2many, tables * should add foreign key column or create intermediate table to make the object * association mapping right. This process will be proceed automatically without * concerning by users. To make this happen, user just need to declare the * associations clearly in the models, and make sure all the mapping models are * added in the litepal.xml file. * * @author Tony Green * @since 1.0 */ public abstract class AssociationCreator extends Generator { protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); /** * {@link org.litepal.tablemanager.AssociationCreator} analyzes two things. Add associations * including add foreign key column to tables and create intermediate join * tables. */ @Override protected void addOrUpdateAssociation(SQLiteDatabase db, boolean force) { addAssociations(getAllAssociations(), db, force); } /** * Generate a create table SQL by the passed in parameters. Note that it * will always generate a SQL with id/_id column in it as primary key and * this id is auto increment as integer if the autoIncrementId is true, or * no primary key will be added. * * @param tableName * The table name. * @param columnModels * A list contains all column models with column info. * @param autoIncrementId * Generate an auto increment id or not. Only intermediate join table doesn't need * an auto increment id. * @return A generated create table SQL. */ protected String generateCreateTableSQL(String tableName, Collection columnModels, boolean autoIncrementId) { StringBuilder createTableSQL = new StringBuilder("create table "); createTableSQL.append(tableName).append(" ("); if (autoIncrementId) { createTableSQL.append("id integer primary key autoincrement,"); } if (isContainsOnlyIdField(columnModels)) { // Remove the last comma when only have id field in model. createTableSQL.deleteCharAt(createTableSQL.length() - 1); } boolean needSeparator = false; for (ColumnModel columnModel : columnModels) { if (columnModel.isIdColumn()) { continue; } if (needSeparator) { createTableSQL.append(", "); } needSeparator = true; createTableSQL.append(columnModel.getColumnName()).append(" ").append(columnModel.getColumnType()); if (!columnModel.isNullable()) { createTableSQL.append(" not null"); } if (columnModel.isUnique()) { createTableSQL.append(" unique"); } String defaultValue = columnModel.getDefaultValue(); if (!TextUtils.isEmpty(defaultValue)) { createTableSQL.append(" default ").append(defaultValue); } } createTableSQL.append(")"); LitePalLog.d(TAG, "create table sql is >> " + createTableSQL); return createTableSQL.toString(); } /** * Generate create index SQLs by the passed in parameters. * * @param tableName * The table name. * @param columnModels * A list contains all column models with column info. * @return A generated create index SQLs. */ protected List generateCreateIndexSQLs(String tableName, Collection columnModels) { List sqls = new ArrayList<>(); for (ColumnModel columnModel : columnModels) { if (columnModel.hasIndex()) { sqls.add(generateCreateIndexSQL(tableName, columnModel)); } } return sqls; } /** * Generate a SQL for dropping table. * * @param tableName * The table name. * @return A SQL to drop table. */ protected String generateDropTableSQL(String tableName) { return "drop table if exists " + tableName; } /** * Generate a SQL for add new column into the existing table. * @param tableName * The table which want to add a column * @param columnModel * Which contains column info * @return A SQL to add new column. */ protected String generateAddColumnSQL(String tableName, ColumnModel columnModel) { StringBuilder addColumnSQL = new StringBuilder(); addColumnSQL.append("alter table ").append(tableName); addColumnSQL.append(" add column ").append(columnModel.getColumnName()); addColumnSQL.append(" ").append(columnModel.getColumnType()); if (!columnModel.isNullable()) { addColumnSQL.append(" not null"); } if (columnModel.isUnique()) { addColumnSQL.append(" unique"); } String defaultValue = columnModel.getDefaultValue(); if (!TextUtils.isEmpty(defaultValue)) { addColumnSQL.append(" default ").append(defaultValue); } else { if (!columnModel.isNullable()) { if ("integer".equalsIgnoreCase(columnModel.getColumnType())) { defaultValue = "0"; } else if ("text".equalsIgnoreCase(columnModel.getColumnType())) { defaultValue = "''"; } else if ("real".equalsIgnoreCase(columnModel.getColumnType())) { defaultValue = "0.0"; } addColumnSQL.append(" default ").append(defaultValue); } } LitePalLog.d(TAG, "add column sql is >> " + addColumnSQL); return addColumnSQL.toString(); } /** * Generate create index SQL by the passed in parameters. * * @param tableName * The table name. * @param columnModel * Column model with column info. * @return A generated create index SQL. */ protected String generateCreateIndexSQL(String tableName, ColumnModel columnModel) { StringBuilder createIndexSQL = new StringBuilder(); if (columnModel.hasIndex()) { createIndexSQL.append("create index "); createIndexSQL.append(DBUtility.getIndexName(tableName, columnModel.getColumnName())); createIndexSQL.append(" on "); createIndexSQL.append(tableName); createIndexSQL.append(" ("); createIndexSQL.append(columnModel.getColumnName()); createIndexSQL.append(")"); LitePalLog.d(TAG, "create table index sql is >> " + createIndexSQL); } return createIndexSQL.toString(); } /** * Judge the passed in column is a foreign key column format or not. Each * column name ends with _id will be considered as foreign key column * format. * * @param columnName * The name of column. * @return Return true if it's foreign column format, otherwise return * false. */ protected boolean isForeignKeyColumnFormat(String columnName) { if (!TextUtils.isEmpty(columnName)) { return columnName.toLowerCase(Locale.US).endsWith("_id") && !columnName.equalsIgnoreCase("_id"); } return false; } /** * Once there's new table created. The table name will be saved into * table_schema as a copy. Each table name will be saved only once. * * @param tableName * The table name. * @param tableType * 0 means normal table, 1 means intermediate join table. * @param db * Instance of SQLiteDatabase. */ protected void giveTableSchemaACopy(String tableName, int tableType, SQLiteDatabase db) { StringBuilder sql = new StringBuilder("select * from "); sql.append(Const.TableSchema.TABLE_NAME); LitePalLog.d(TAG, "giveTableSchemaACopy SQL is >> " + sql); Cursor cursor = null; try { cursor = db.rawQuery(sql.toString(), null); if (isNeedtoGiveACopy(cursor, tableName)) { ContentValues values = new ContentValues(); values.put(Const.TableSchema.COLUMN_NAME, BaseUtility.changeCase(tableName)); values.put(Const.TableSchema.COLUMN_TYPE, tableType); db.insert(Const.TableSchema.TABLE_NAME, null, values); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } /** * Save the name of a created table into table_schema, but there're some * extra rules. Each table name should be only saved once, and special * tables will not be saved. * * @param cursor * The cursor used to iterator values in the table. * @param tableName * The table name. * @return If all rules are passed return true, any of them failed return * false. */ private boolean isNeedtoGiveACopy(Cursor cursor, String tableName) { return !isValueExists(cursor, tableName) && !isSpecialTable(tableName); } /** * Judge the table name has already exist in the table_schema or not. * * @param cursor * The cursor used to iterator values in the table. * @param tableName * The table name. * @return If value exists return true, or return false. */ private boolean isValueExists(Cursor cursor, String tableName) { boolean exist = false; if (cursor.moveToFirst()) { do { String name = cursor.getString(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); if (name.equalsIgnoreCase(tableName)) { exist = true; break; } } while (cursor.moveToNext()); } return exist; } /** * Judge a table is a special table or not. Currently table_schema is a * special table. * * @param tableName * The table name. * @return Return true if it's special table. */ private boolean isSpecialTable(String tableName) { return Const.TableSchema.TABLE_NAME.equalsIgnoreCase(tableName); } /** * Analyzing all the association models in the collection. Judge their * association types. If it's one2one or many2one associations, add the * foreign key column to the associated table. If it's many2many * associations, create an intermediate join table. * * @param associatedModels * A collection contains all the association models.Use the * association models to get association type and associated * table names. * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private void addAssociations(Collection associatedModels, SQLiteDatabase db, boolean force) { for (AssociationsModel associationModel : associatedModels) { if (Const.Model.MANY_TO_ONE == associationModel.getAssociationType() || Const.Model.ONE_TO_ONE == associationModel.getAssociationType()) { addForeignKeyColumn(associationModel.getTableName(), associationModel.getAssociatedTableName(), associationModel.getTableHoldsForeignKey(), db); } else if (Const.Model.MANY_TO_MANY == associationModel.getAssociationType()) { createIntermediateTable(associationModel.getTableName(), associationModel.getAssociatedTableName(), db, force); } } for (GenericModel genericModel : getGenericModels()) { createGenericTable(genericModel, db, force); } } /** * When it comes to many2many associations. Database need to create an * intermediate table for mapping this association. This method helps create * such a table, and the table name follows the concatenation of the two * target table names in alphabetical order with underline in the middle. * * @param tableName * The table name. * @param associatedTableName * The associated table name. * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private void createIntermediateTable(String tableName, String associatedTableName, SQLiteDatabase db, boolean force) { List columnModelList = new ArrayList<>(); ColumnModel column1 = new ColumnModel(); column1.setColumnName(tableName + "_id"); column1.setColumnType("integer"); ColumnModel column2 = new ColumnModel(); column2.setColumnName(associatedTableName + "_id"); column2.setColumnType("integer"); columnModelList.add(column1); columnModelList.add(column2); String intermediateTableName = DBUtility.getIntermediateTableName(tableName, associatedTableName); List sqls = new ArrayList<>(); if (DBUtility.isTableExists(intermediateTableName, db)) { if (force) { sqls.add(generateDropTableSQL(intermediateTableName)); sqls.add(generateCreateTableSQL(intermediateTableName, columnModelList, false)); } } else { sqls.add(generateCreateTableSQL(intermediateTableName, columnModelList, false)); } execute(sqls, db); giveTableSchemaACopy(intermediateTableName, Const.TableSchema.INTERMEDIATE_JOIN_TABLE, db); } /** * When declared generic collection fields in model class. Database need to create * generic tables for mapping these fields. This method helps create such a table. * * @param genericModel * The GenericModel instance. * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private void createGenericTable(GenericModel genericModel, SQLiteDatabase db, boolean force) { String tableName = genericModel.getTableName(); String valueColumnName = genericModel.getValueColumnName(); String valueColumnType = genericModel.getValueColumnType(); String valueIdColumnName = genericModel.getValueIdColumnName(); List columnModelList = new ArrayList<>(); ColumnModel column1 = new ColumnModel(); column1.setColumnName(valueColumnName); column1.setColumnType(valueColumnType); ColumnModel column2 = new ColumnModel(); column2.setColumnName(valueIdColumnName); column2.setColumnType("integer"); columnModelList.add(column1); columnModelList.add(column2); List sqls = new ArrayList<>(); if (DBUtility.isTableExists(tableName, db)) { if (force) { sqls.add(generateDropTableSQL(tableName)); sqls.add(generateCreateTableSQL(tableName, columnModelList, false)); } } else { sqls.add(generateCreateTableSQL(tableName, columnModelList, false)); } execute(sqls, db); giveTableSchemaACopy(tableName, Const.TableSchema.GENERIC_TABLE, db); } /** * This method is used to add many to one association or one to one * association on tables. It will automatically build a SQL to add foreign * key to a table. If the passed in table name or associated table name * doesn't exist, it will throw an exception. * * @param tableName * The table name. * @param associatedTableName * The associated table name. * @param tableHoldsForeignKey * The table which holds the foreign key. * @param db * Instance of SQLiteDatabase. */ protected void addForeignKeyColumn(String tableName, String associatedTableName, String tableHoldsForeignKey, SQLiteDatabase db) { if (DBUtility.isTableExists(tableName, db)) { if (DBUtility.isTableExists(associatedTableName, db)) { String foreignKeyColumn = null; if (tableName.equals(tableHoldsForeignKey)) { foreignKeyColumn = getForeignKeyColumnName(associatedTableName); } else if (associatedTableName.equals(tableHoldsForeignKey)) { foreignKeyColumn = getForeignKeyColumnName(tableName); } if (!DBUtility.isColumnExists(foreignKeyColumn, tableHoldsForeignKey, db)) { ColumnModel columnModel = new ColumnModel(); columnModel.setColumnName(foreignKeyColumn); columnModel.setColumnType("integer"); List sqls = new ArrayList<>(); sqls.add(generateAddColumnSQL(tableHoldsForeignKey, columnModel)); execute(sqls, db); } else { LitePalLog.d(TAG, "column " + foreignKeyColumn + " is already exist, no need to add one"); } } else { throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + associatedTableName); } } else { throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + tableName); } } /** * Check if the ColumnModel list contains only id field. * @param columnModels * List contains model fields. * @return If ColumnModel list is empty or contains only id, _id field, return true. Otherwise return false. */ private boolean isContainsOnlyIdField(Collection columnModels) { for (ColumnModel columnModel : columnModels) { if (!columnModel.isIdColumn()) return false; } return true; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/AssociationUpdater.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.litepal.parser.LitePalAttr; import org.litepal.tablemanager.model.AssociationsModel; import org.litepal.tablemanager.model.ColumnModel; import org.litepal.tablemanager.model.GenericModel; import org.litepal.tablemanager.model.TableModel; import org.litepal.util.Const; import org.litepal.util.DBUtility; import org.litepal.util.BaseUtility; import org.litepal.util.LitePalLog; import android.database.sqlite.SQLiteDatabase; /** * Upgrade the associations between model classes into tables. Creating new * tables and adding new foreign key columns are done in * {@link org.litepal.tablemanager.AssociationUpdater}. So this class just deal with the simple job of * removing foreign key columns and dropping dump intermediate join tables. * * @author Tony Green * @since 1.0 */ public abstract class AssociationUpdater extends Creator { public static final String TAG = "AssociationUpdater"; /** * A collection contains all the association models. */ private Collection mAssociationModels; /** * Instance of SQLiteDatabase. */ protected SQLiteDatabase mDb; /** * Analysis the {@link org.litepal.tablemanager.model.TableModel} by the purpose of subclasses, and * generate a SQL to do the intention job. The implementation of this method * is totally delegated to the subclasses. */ @Override protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); /** * {@link org.litepal.tablemanager.AssociationUpdater} does two jobs. Removing foreign key columns * when two models are not associated anymore, and remove the intermediate * join tables when two models are not associated anymore. */ @Override protected void addOrUpdateAssociation(SQLiteDatabase db, boolean force) { mAssociationModels = getAllAssociations(); mDb = db; removeAssociations(); } /** * This method looks around all the columns in the table, and judge which of * them are foreign key columns. * * @param tableModel * Use the TableModel to get table name and columns name to * generate SQL. * @return All the foreign key columns in a list. */ protected List getForeignKeyColumns(TableModel tableModel) { List foreignKeyColumns = new ArrayList<>(); Collection columnModels = getTableModelFromDB(tableModel.getTableName()).getColumnModels(); for (ColumnModel columnModel : columnModels) { String columnName = columnModel.getColumnName(); if (isForeignKeyColumnFormat(columnModel.getColumnName())) { if (!tableModel.containsColumn(columnName)) { // Now this is a foreign key column. LitePalLog.d(TAG, "getForeignKeyColumnNames >> foreign key column is " + columnName); foreignKeyColumns.add(columnName); } } } return foreignKeyColumns; } /** * Judge the passed in column is a foreign key column or not. Each column * name ends with _id will be considered as foreign key column. * * @param tableModel * Use the TableModel to get table name and columns name to * generate SQL. * @param columnName * The column to judge. * @return Return true if it's foreign column, otherwise return false. */ protected boolean isForeignKeyColumn(TableModel tableModel, String columnName) { return BaseUtility.containsIgnoreCases(getForeignKeyColumns(tableModel), columnName); } /** * Look from the database to find a table named same as the table name in * table model. Then iterate the columns and types of this table to create a * new instance of table model. If there's no such a table in the database, * then throw DatabaseGenerateException. * * @param tableName * The table name use to get table model from database. * @return A table model object with values from database table. */ protected TableModel getTableModelFromDB(String tableName) { return DBUtility.findPragmaTableInfo(tableName, mDb); } /** * Drop the tables by the passing table name. * * @param dropTableNames * The names of the tables that need to drop. * @param db * Instance of SQLiteDatabase. */ protected void dropTables(List dropTableNames, SQLiteDatabase db) { if (dropTableNames != null && !dropTableNames.isEmpty()) { List dropTableSQLS = new ArrayList<>(); for (int i = 0; i < dropTableNames.size(); i++) { dropTableSQLS.add(generateDropTableSQL(dropTableNames.get(i))); } execute(dropTableSQLS, db); } } /** * When some fields are removed from class, the table should synchronize the * changes by removing the corresponding columns. * * @param removeColumnNames * The column names that need to remove. * @param tableName * The table name to remove columns from. */ protected void removeColumns(Collection removeColumnNames, String tableName) { if (removeColumnNames != null && !removeColumnNames.isEmpty()) { execute(getRemoveColumnSQLs(removeColumnNames, tableName), mDb); } } /** * The values in table_schame should be synchronized with the model tables * in the database. If a model table is dropped, the corresponding data * should be removed from table_schema too. * * @param tableNames * The table names need to remove from table_schema. */ protected void clearCopyInTableSchema(List tableNames) { if (tableNames != null && !tableNames.isEmpty()) { StringBuilder deleteData = new StringBuilder("delete from "); deleteData.append(Const.TableSchema.TABLE_NAME).append(" where"); boolean needOr = false; for (String tableName : tableNames) { if (needOr) { deleteData.append(" or "); } needOr = true; deleteData.append(" lower(").append(Const.TableSchema.COLUMN_NAME).append(") "); deleteData.append("=").append(" lower('").append(tableName).append("')"); } LitePalLog.d(TAG, "clear table schema value sql is " + deleteData); List sqls = new ArrayList<>(); sqls.add(deleteData.toString()); execute(sqls, mDb); } } /** * When the association between two tables are no longer associated in the * classes, database should remove the foreign key column or intermediate * join table that keeps these two tables associated. */ private void removeAssociations() { removeForeignKeyColumns(); removeIntermediateTables(); removeGenericTables(); } /** * Analyzing the table models, then remove all the foreign key columns if * their association in model classes are no longer exist any more. */ private void removeForeignKeyColumns() { for (String className : LitePalAttr.getInstance().getClassNames()) { TableModel tableModel = getTableModel(className); removeColumns(findForeignKeyToRemove(tableModel), tableModel.getTableName()); } } /** * If there're intermediate join tables for two tables, when the two classes * are not associated, the join table should be dropped. */ private void removeIntermediateTables() { List tableNamesToDrop = findIntermediateTablesToDrop(); dropTables(tableNamesToDrop, mDb); clearCopyInTableSchema(tableNamesToDrop); } /** * If there're generic tables for generic fields, when the fields are removed * from class, the generic tables should be dropped. */ private void removeGenericTables() { List tableNamesToDrop = findGenericTablesToDrop(); dropTables(tableNamesToDrop, mDb); clearCopyInTableSchema(tableNamesToDrop); } /** * This method gives back the names of the foreign key columns that need to * remove, cause their associations in the classes are no longer exist. * * @param tableModel * Use the TableModel to get table name and columns name to * generate SQL. * @return The foreign key columns need to remove in a list. */ private List findForeignKeyToRemove(TableModel tableModel) { List removeRelations = new ArrayList<>(); List foreignKeyColumns = getForeignKeyColumns(tableModel); String selfTableName = tableModel.getTableName(); for (String foreignKeyColumn : foreignKeyColumns) { String associatedTableName = DBUtility.getTableNameByForeignColumn(foreignKeyColumn); if (shouldDropForeignKey(selfTableName, associatedTableName)) { removeRelations.add(foreignKeyColumn); } } LitePalLog.d(TAG, "findForeignKeyToRemove >> " + tableModel.getTableName() + " " + removeRelations); return removeRelations; } /** * When many2many associations are no longer exist between two models, the * intermediate join table should be dropped from database. This method * helps find out those intermediate join tables which should be dropped * cause their associations in classes are done. * * @return A list with all intermediate join tables to drop. */ private List findIntermediateTablesToDrop() { List intermediateTables = new ArrayList<>(); for (String tableName : DBUtility.findAllTableNames(mDb)) { if (DBUtility.isIntermediateTable(tableName, mDb)) { boolean dropIntermediateTable = true; for (AssociationsModel associationModel : mAssociationModels) { if (associationModel.getAssociationType() == Const.Model.MANY_TO_MANY) { String intermediateTableName = DBUtility.getIntermediateTableName( associationModel.getTableName(), associationModel.getAssociatedTableName()); if (tableName.equalsIgnoreCase(intermediateTableName)) { dropIntermediateTable = false; } } } if (dropIntermediateTable) { // drop the intermediate join table intermediateTables.add(tableName); } } } LitePalLog.d(TAG, "findIntermediateTablesToDrop >> " + intermediateTables); return intermediateTables; } /** * When generic fields are no longer exist in the class models, the generic tables should be * dropped from database. This method helps find out those generic tables which should be dropped * cause their generic fields in classes are removed. * * @return A list with all generic tables to drop. */ private List findGenericTablesToDrop() { List genericTablesToDrop = new ArrayList<>(); for (String tableName : DBUtility.findAllTableNames(mDb)) { if (DBUtility.isGenericTable(tableName, mDb)) { boolean dropGenericTable = true; for (GenericModel genericModel : getGenericModels()) { String genericTableName = genericModel.getTableName(); if (tableName.equalsIgnoreCase(genericTableName)) { dropGenericTable = false; break; } } if (dropGenericTable) { // drop the generic table genericTablesToDrop.add(tableName); } } } return genericTablesToDrop; } /** * Generate a SQL for renaming the table into a temporary table. * * @param tableName * The table name use to alter to temporary table. * @return SQL to rename table. */ protected String generateAlterToTempTableSQL(String tableName) { StringBuilder sql = new StringBuilder(); sql.append("alter table ").append(tableName).append(" rename to ") .append(getTempTableName(tableName)); return sql.toString(); } /** * Generate a SQL to create new table by the table model from database. Also * it will remove the columns that need to remove before generating the SQL. * * @param removeColumnNames * The column names need to remove. * @param tableModel * Which contains table name use to create new table. * @return SQL to create new table. */ private String generateCreateNewTableSQL(Collection removeColumnNames, TableModel tableModel) { for (String removeColumnName : removeColumnNames) { tableModel.removeColumnModelByName(removeColumnName); } return generateCreateTableSQL(tableModel); } /** * Generate a SQL to do the data migration job to avoid losing data. * * @param tableModel * Which contains table name use to migrate data. * @return SQL to migrate data. */ protected String generateDataMigrationSQL(TableModel tableModel) { String tableName = tableModel.getTableName(); Collection columnModels = tableModel.getColumnModels(); if (!columnModels.isEmpty()) { StringBuilder sql = new StringBuilder(); sql.append("insert into ").append(tableName).append("("); boolean needComma = false; for (ColumnModel columnModel : columnModels) { if (needComma) { sql.append(", "); } needComma = true; sql.append(columnModel.getColumnName()); } sql.append(") "); sql.append("select "); needComma = false; for (ColumnModel columnModel : columnModels) { if (needComma) { sql.append(", "); } needComma = true; sql.append(columnModel.getColumnName()); } sql.append(" from ").append(getTempTableName(tableName)); return sql.toString(); } else { return null; } } /** * Generate a SQL to drop the temporary table. * * @param tableName * The table name use to drop temporary table. * @return SQL to drop the temporary table. */ protected String generateDropTempTableSQL(String tableName) { return generateDropTableSQL(getTempTableName(tableName)); } /** * Removing or resizing columns from tables must need a temporary table to * store data, and here's the table name. * * @param tableName * The table name use to generate temporary table name. * @return Temporary table name */ protected String getTempTableName(String tableName) { return tableName + "_temp"; } /** * This method create a SQL array for the whole remove dump columns job. * * @param removeColumnNames * The column names need to remove. * @param tableName * The table name to remove from. * @return A SQL list contains create temporary table, create new table, * migrate data and drop temporary table. */ private List getRemoveColumnSQLs(Collection removeColumnNames, String tableName) { TableModel tableModelFromDB = getTableModelFromDB(tableName); String alterToTempTableSQL = generateAlterToTempTableSQL(tableName); LitePalLog.d(TAG, "generateRemoveColumnSQL >> " + alterToTempTableSQL); String createNewTableSQL = generateCreateNewTableSQL(removeColumnNames, tableModelFromDB); LitePalLog.d(TAG, "generateRemoveColumnSQL >> " + createNewTableSQL); String dataMigrationSQL = generateDataMigrationSQL(tableModelFromDB); LitePalLog.d(TAG, "generateRemoveColumnSQL >> " + dataMigrationSQL); String dropTempTableSQL = generateDropTempTableSQL(tableName); LitePalLog.d(TAG, "generateRemoveColumnSQL >> " + dropTempTableSQL); List createIndexSQLs = generateCreateIndexSQLs(tableModelFromDB); List sqls = new ArrayList<>(); sqls.add(alterToTempTableSQL); sqls.add(createNewTableSQL); sqls.add(dataMigrationSQL); sqls.add(dropTempTableSQL); sqls.addAll(createIndexSQLs); return sqls; } /** * Judge if the current iterated foreign key column should be dropped. It is * only used in {@link #findForeignKeyToRemove(org.litepal.tablemanager.model.TableModel)} when iterating * the foreign key column list. When this foreign key can not be found in * the association model collection, this foreign key should be dropped. * * @param selfTableName * The table name of currently table model. * @param associatedTableName * The associated table name of current table model. * @return If the foreign key currently iterated should be dropped, return * true. Otherwise return false. */ private boolean shouldDropForeignKey(String selfTableName, String associatedTableName) { for (AssociationsModel associationModel : mAssociationModels) { if (associationModel.getAssociationType() == Const.Model.ONE_TO_ONE) { if (selfTableName.equalsIgnoreCase(associationModel.getTableHoldsForeignKey())) { if (associationModel.getTableName().equalsIgnoreCase(selfTableName)) { if (isRelationCorrect(associationModel, selfTableName, associatedTableName)) { return false; } } else if (associationModel.getAssociatedTableName().equalsIgnoreCase( selfTableName)) { if (isRelationCorrect(associationModel, associatedTableName, selfTableName)) { return false; } } } } else if (associationModel.getAssociationType() == Const.Model.MANY_TO_ONE) { if (isRelationCorrect(associationModel, associatedTableName, selfTableName)) { return false; } } } return true; } /** * Judge if the tableName1 equals {@link org.litepal.tablemanager.model.AssociationsModel#getTableName()} * and tableName2 equals {@link org.litepal.tablemanager.model.AssociationsModel#getAssociatedTableName()}. * * @param associationModel * The association model to get table name and associated table * name. * @param tableName1 * The table name to match table name from association model. * @param tableName2 * The table name to match associated table name from association * model. * @return Return true if the description is true, otherwise return false. */ private boolean isRelationCorrect(AssociationsModel associationModel, String tableName1, String tableName2) { return associationModel.getTableName().equalsIgnoreCase(tableName1) && associationModel.getAssociatedTableName().equalsIgnoreCase(tableName2); } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/Connector.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import android.database.sqlite.SQLiteDatabase; import android.os.Environment; import android.text.TextUtils; import org.litepal.LitePalApplication; import org.litepal.parser.LitePalAttr; import java.io.File; /** * The connector to connect database provided by LitePal. Users can use this * class to get the instance of SQLiteDatabase. But users still need to write * their own CRUD logic by the returned SQLiteDatabase. It will be improved in * the future. * * @author Tony Green * @since 1.0 */ public class Connector { /** * The quote of LitePalHelper. */ private static LitePalOpenHelper mLitePalHelper; /** * Get a writable SQLiteDatabase. * * There're a lot of ways to operate database in android. But LitePal * doesn't support using ContentProvider currently. The best way to use * LitePal well is get the SQLiteDatabase instance and use the methods like * SQLiteDatabase#save, SQLiteDatabase#update, SQLiteDatabase#delete, * SQLiteDatabase#query in the SQLiteDatabase class to do the database * operation. It will be improved in the future. * * @return A writable SQLiteDatabase instance */ public synchronized static SQLiteDatabase getWritableDatabase() { LitePalOpenHelper litePalHelper = buildConnection(); return litePalHelper.getWritableDatabase(); } /** * Call getDatabase directly will invoke the getWritableDatabase method by * default. * * This is method is alias of getWritableDatabase. * * @return A writable SQLiteDatabase instance */ public static SQLiteDatabase getDatabase() { return getWritableDatabase(); } /** * Build a connection to the database. This progress will analysis the * litepal.xml file, and will check if the fields in LitePalAttr are valid, * and it will open a SQLiteOpenHelper to decide to create tables or update * tables or doing nothing depends on the version attributes. * * After all the stuffs above are finished. This method will return a * LitePalHelper object.Notes this method could throw a lot of exceptions. * * @return LitePalHelper object. */ private static LitePalOpenHelper buildConnection() { LitePalAttr litePalAttr = LitePalAttr.getInstance(); litePalAttr.checkSelfValid(); if (mLitePalHelper == null) { String dbName = litePalAttr.getDbName(); if ("external".equalsIgnoreCase(litePalAttr.getStorage())) { dbName = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/" + dbName; } else if (!"internal".equalsIgnoreCase(litePalAttr.getStorage()) && !TextUtils.isEmpty(litePalAttr.getStorage())) { // internal or empty means internal storage, neither or them means sdcard storage String dbPath = Environment.getExternalStorageDirectory().getPath() + "/" + litePalAttr.getStorage(); dbPath = dbPath.replace("//", "/"); File path = new File(dbPath); if (!path.exists()) { path.mkdirs(); } dbName = dbPath + "/" + dbName; } mLitePalHelper = new LitePalOpenHelper(dbName, litePalAttr.getVersion()); } return mLitePalHelper; } /** * Never call this method. This is only used by internal. */ public static void clearLitePalOpenHelperInstance() { if (mLitePalHelper != null) { mLitePalHelper.getWritableDatabase().close(); mLitePalHelper = null; } } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/Creator.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import org.litepal.tablemanager.model.TableModel; import org.litepal.util.Const; import org.litepal.util.DBUtility; import android.database.sqlite.SQLiteDatabase; import java.util.ArrayList; import java.util.List; /** * This is a subclass of Generator. Use to create tables. It will automatically * build a create table SQL based on the passing TableModel object. In case of * there's already a table with the same name in the database, LitePal will * always drop the table first before create a new one. If there's syntax error * in the executing SQL by accident, Creator will throw a * DatabaseGenerateException. * * @author Tony Green * @since 1.0 */ class Creator extends AssociationCreator { public static final String TAG = "Creator"; /** * Analyzing the table model, create a table in the database based on the * table model's value. */ @Override protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) { for (TableModel tableModel : getAllTableModels()) { createOrUpgradeTable(tableModel, db, force); } } protected void createOrUpgradeTable(TableModel tableModel, SQLiteDatabase db, boolean force) { execute(getCreateTableSQLs(tableModel, db, force), db); giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db); } /** * When creating a new table, it should always try to drop the same name * table if exists. This method create a SQL array for the whole create * table job. * * @param tableModel * The table model. * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. * @return A SQL array contains drop table if it exists and create new * table. */ protected List getCreateTableSQLs(TableModel tableModel, SQLiteDatabase db, boolean force) { List sqls = new ArrayList<>(); if (force) { sqls.add(generateDropTableSQL(tableModel)); sqls.add(generateCreateTableSQL(tableModel)); } else { if (DBUtility.isTableExists(tableModel.getTableName(), db)) { return null; } else { sqls.add(generateCreateTableSQL(tableModel)); } } sqls.addAll(generateCreateIndexSQLs(tableModel)); // create index after create table return sqls; } /** * Generate a SQL for dropping table. * * @param tableModel * The table model. * @return A SQL to drop table. */ private String generateDropTableSQL(TableModel tableModel) { return generateDropTableSQL(tableModel.getTableName()); } /** * Generate a create table SQL by analyzing the TableModel. Note that it * will always generate a SQL with id/_id column in it as primary key, and * this id is auto increment as integer. Do not try to assign or modify it. * * @param tableModel * Use the TableModel to get table name and columns name to * generate SQL. * @return A generated create table SQL. */ String generateCreateTableSQL(TableModel tableModel) { return generateCreateTableSQL(tableModel.getTableName(), tableModel.getColumnModels(), true); } /** * Generate create index SQLs by analyzing the TableModel. * * @param tableModel * Use the TableModel to get table name and columns name to * generate SQLs. * @return A generated create index SQLs. */ List generateCreateIndexSQLs(TableModel tableModel) { return generateCreateIndexSQLs(tableModel.getTableName(), tableModel.getColumnModels()); } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/Dropper.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.litepal.tablemanager.model.TableModel; import org.litepal.util.BaseUtility; import org.litepal.util.Const; import org.litepal.util.LitePalLog; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; /** * When developers defined some model classes and define them in the mapping * list. All corresponding tables will be created automatically. But developers * might realize some model classes are useless or can be optimized to remove, * and they somehow drop the model classes. If the tables are still in the * database, it will be a mess soon. So this class helps do the dropping job. * Keep developers' database synchronized and clean. * * @author Tony Green * @since 1.0 */ public class Dropper extends AssociationUpdater { /** * Use the TableModel to get table name and columns name to generate SQL. */ private Collection mTableModels; /** * Analyzing the table model, to see which tables has no model classes * anymore and can be dropped. */ @Override protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) { mTableModels = getAllTableModels(); mDb = db; dropTables(); } /** * Drop the tables which are not exist in the mapping list to keep * synchronization. */ private void dropTables() { List tableNamesToDrop = findTablesToDrop(); dropTables(tableNamesToDrop, mDb); clearCopyInTableSchema(tableNamesToDrop); } /** * It will find all the tables need to drop in the database, following the * rules of {@link #shouldDropThisTable(String, int)}. * * @return A list contains all the table names need to drop. */ private List findTablesToDrop() { List dropTableNames = new ArrayList<>(); Cursor cursor = null; try { cursor = mDb.query(Const.TableSchema.TABLE_NAME, null, null, null, null, null, null); if (cursor.moveToFirst()) { do { String tableName = cursor.getString(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); int tableType = cursor.getInt(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_TYPE)); if (shouldDropThisTable(tableName, tableType)) { // need to drop tableNameDB LitePalLog.d(TAG, "need to drop " + tableName); dropTableNames.add(tableName); } } while (cursor.moveToNext()); } } catch (Exception ex) { ex.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } return dropTableNames; } /** * Get a list only with table names. * * @return A list only contains table names. */ private List pickTableNamesFromTableModels() { List tableNames = new ArrayList<>(); for (TableModel tableModel : mTableModels) { tableNames.add(tableModel.getTableName()); } return tableNames; } /** * It gets all the table names generated by the mapping classes and create a * table name list. Compare table name list with the table name passed in. * If the table name is not existed in the table name list and the table * type is {@link org.litepal.util.Const.TableSchema#NORMAL_TABLE}, then this table should be * dropped. * * @param tableName * The table name to check. * @param tableType * The table type to check. * @return If this table should be dropped return true. Otherwise return * false. */ private boolean shouldDropThisTable(String tableName, int tableType) { return !BaseUtility.containsIgnoreCases(pickTableNamesFromTableModels(), tableName) && tableType == Const.TableSchema.NORMAL_TABLE; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/Generator.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.litepal.LitePalBase; import org.litepal.exceptions.DatabaseGenerateException; import org.litepal.parser.LitePalAttr; import org.litepal.tablemanager.model.AssociationsModel; import org.litepal.tablemanager.model.TableModel; import org.litepal.util.BaseUtility; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; /** * This class is the basic class for managing database dynamically. It is used * to create or update tables by the mapping classes from litepal.xml file. * Generator is a superclass, it just read the fields from classes and format * the fields types into database types. The analysis job is delegated to the * subclasses to do. Then subclasses can invoke the execute method to finish the * job or they can override to do their own logic. * * @author Tony Green * @since 1.0 */ public abstract class Generator extends LitePalBase { public static final String TAG = "Generator"; /** * The collection contains all table models. Use a global variable store * table model to improve performance. Avoiding look up for table model each * time. */ private Collection mTableModels; /** * The collection contains all association models. */ private Collection mAllRelationModels; /** * This is a shortcut way to get all the table models for each model class * defined in the mapping list. No need to iterate all the model classes and * get table model for each one. * * @return A collection contains all table models. */ protected Collection getAllTableModels() { if (mTableModels == null) { mTableModels = new ArrayList<>(); } if (!canUseCache()) { mTableModels.clear(); for (String className : LitePalAttr.getInstance().getClassNames()) { mTableModels.add(getTableModel(className)); } } return mTableModels; } /** * This method is used to get all the association models which in the * mapping list of litepal.xml file. * * @return Collection of RelationModel for all the mapping classes. */ protected Collection getAllAssociations() { if (mAllRelationModels == null || mAllRelationModels.isEmpty()) { mAllRelationModels = getAssociations(LitePalAttr.getInstance().getClassNames()); } return mAllRelationModels; } /** * Use the parameter SQLiteDatabase to execute the passing SQLs. Subclasses * can add their own logic when do the executing job by overriding this * method. * * @param sqls * SQLs that want to execute. * @param db * instance of SQLiteDatabase */ protected void execute(List sqls, SQLiteDatabase db) { String throwSQL = ""; try { if (sqls != null && !sqls.isEmpty()) { for (String sql : sqls) { if (!TextUtils.isEmpty(sql)) { throwSQL = BaseUtility.changeCase(sql); db.execSQL(throwSQL); } } } } catch (SQLException e) { throw new DatabaseGenerateException(DatabaseGenerateException.SQL_ERROR + throwSQL); } } /** * Add association to all the tables based on the associations between class * models. * * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private static void addAssociation(SQLiteDatabase db, boolean force) { AssociationCreator associationsCreator = new Creator(); associationsCreator.addOrUpdateAssociation(db, force); } /** * Update associations to all the associated tables in the database. Remove * dump foreign key columns and dump intermediate join tables. * * @param db * Instance of SQLiteDatabase. */ private static void updateAssociations(SQLiteDatabase db) { AssociationUpdater associationUpgrader = new Upgrader(); associationUpgrader.addOrUpdateAssociation(db, false); } /** * Upgrade all the tables in the database, including remove dump columns and * add new columns. * * @param db * Instance of SQLiteDatabase. */ private static void upgradeTables(SQLiteDatabase db) { Upgrader upgrader = new Upgrader(); upgrader.createOrUpgradeTable(db, false); } /** * Create tables based on the class models defined in the litepal.xml file. * After the tables are created, add association to these tables based on * the associations between class models. * * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ private static void create(SQLiteDatabase db, boolean force) { Creator creator = new Creator(); creator.createOrUpgradeTable(db, force); } /** * Drop the tables which are no longer exist in the mapping list but created * before. * * @param db * Instance of SQLiteDatabase. */ private static void drop(SQLiteDatabase db) { Dropper dropper = new Dropper(); dropper.createOrUpgradeTable(db, false); } /** * If the table models in the collection has the same size as classes * defined in the mapping list, it means that the table models are exist. * Can use cache. * * @return Can use the cache for the table models or not. */ private boolean canUseCache() { if (mTableModels == null) { return false; } return mTableModels.size() == LitePalAttr.getInstance().getClassNames().size(); } /** * Create tables based on the class models defined in the litepal.xml file. * After the tables are created, add association to these tables based on * the associations between class models. * * @param db * Instance of SQLiteDatabase. */ static void create(SQLiteDatabase db) { create(db, true); addAssociation(db, true); } /** * Upgrade tables to make sure when model classes are changed, the * corresponding tables in the database should be always synchronized with * them. * * @param db * Instance of SQLiteDatabase. */ static void upgrade(SQLiteDatabase db) { drop(db); create(db, false); updateAssociations(db); upgradeTables(db); addAssociation(db, false); } /** * Analysis the TableModel by the purpose of subclasses, and generate a SQL * to do the intention job. The implementation of this method is totally * delegated to the subclasses. * * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); /** * Analysis the {@link org.litepal.tablemanager.model.AssociationsModel} by the purpose of subclasses, and * generate a SQL to do the intention job. The implementation of this method * is totally delegated to the subclasses. * * @param db * Instance of SQLiteDatabase. * @param force * Drop the table first if it already exists. */ protected abstract void addOrUpdateAssociation(SQLiteDatabase db, boolean force); } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/LitePalOpenHelper.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import org.litepal.LitePalApplication; import org.litepal.Operator; import org.litepal.parser.LitePalAttr; import org.litepal.tablemanager.callback.DatabaseListener; import org.litepal.util.SharedUtil; /** * The database helper to generate and manage the tables. It will automate * create or upgrade the database file depends on the parameters passed in. * * LitePal makes it easy for managing tables. It used the dynamic features of * Java with reflection API to achieve that. Developers won't need to write * their own SQL for managing tables, LitePal will do that for them. Developers * just need to write their model classes and add right associations. LitePal * will take all the rest job to manager tables in database. * * @author Tony Green * @since 1.0 */ class LitePalOpenHelper extends SQLiteOpenHelper { public static final String TAG = "LitePalHelper"; /** * The standard constructor for SQLiteOpenHelper. * * @param context * To use to open or create the database. * @param name * The database file. * @param factory * To use for creating cursor objects, or null for the default * version number of the database (starting at 1); if the * database is older, onUpgrade. * @param version * (SQLiteDatabase, int, int) will be used to upgrade the * database; if the database is newer, * onDowngrade(SQLiteDatabase, int, int) will be used to * downgrade the database */ LitePalOpenHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } /** * A simple constructor for SQLiteOpenHelper with null for CursorFactory as * default. * * @param dbName * The database file. * @param version * (SQLiteDatabase, int, int) will be used to upgrade the * database; if the database is newer, * onDowngrade(SQLiteDatabase, int, int) will be used to * downgrade the database */ LitePalOpenHelper(String dbName, int version) { this(LitePalApplication.getContext(), dbName, null, version); } @Override public void onCreate(SQLiteDatabase db) { Generator.create(db); final DatabaseListener listener = Operator.getDBListener(); if (listener != null) { LitePalApplication.sHandler.post(new Runnable() { @Override public void run() { listener.onCreate(); } }); } } @Override public void onUpgrade(SQLiteDatabase db, final int oldVersion, final int newVersion) { Generator.upgrade(db); SharedUtil.updateVersion(LitePalAttr.getInstance().getExtraKeyName(), newVersion); final DatabaseListener listener = Operator.getDBListener(); if (listener != null) { LitePalApplication.sHandler.post(new Runnable() { @Override public void run() { listener.onUpgrade(oldVersion, newVersion); } }); } } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/Upgrader.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import org.litepal.crud.model.AssociationsInfo; import org.litepal.tablemanager.model.ColumnModel; import org.litepal.tablemanager.model.TableModel; import org.litepal.util.Const; import org.litepal.util.DBUtility; import org.litepal.util.LitePalLog; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Upgrade the database. The first step is to remove the columns that can not * find the corresponding field in the model class. Then add the new added field * as new column into the table. At last it will check all the types of columns * to see which are changed. * * @author Tony Green * @since 1.0 */ public class Upgrader extends AssociationUpdater { /** * Model class for table. */ protected TableModel mTableModel; /** * Model class for table from database. */ protected TableModel mTableModelDB; /** * Indicates that column constraints has changed or not. */ private boolean hasConstraintChanged; /** * Analyzing the table model, them remove the dump columns and add new * columns of a table. */ @Override protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) { mDb = db; for (TableModel tableModel : getAllTableModels()) { mTableModel = tableModel; mTableModelDB = getTableModelFromDB(tableModel.getTableName()); LitePalLog.d(TAG, "createOrUpgradeTable: model is " + mTableModel.getTableName()); upgradeTable(); } } /** * Upgrade table actions. Include remove dump columns, add new columns and * change column types. All the actions above will be done by the description * order. */ private void upgradeTable() { if (hasNewUniqueOrNotNullColumn()) { // Need to drop the table and create new one. Cause unique column can not be added, and null data can not be migrated. createOrUpgradeTable(mTableModel, mDb, true); // add foreign keys of the table. Collection associationsInfo = getAssociationInfo(mTableModel.getClassName()); for (AssociationsInfo info : associationsInfo) { if (info.getAssociationType() == Const.Model.MANY_TO_ONE || info.getAssociationType() == Const.Model.ONE_TO_ONE) { if (info.getClassHoldsForeignKey().equalsIgnoreCase(mTableModel.getClassName())) { String associatedTableName = DBUtility.getTableNameByClassName(info.getAssociatedClassName()); addForeignKeyColumn(mTableModel.getTableName(), associatedTableName, mTableModel.getTableName(), mDb); } } } } else { hasConstraintChanged = false; removeColumns(findColumnsToRemove()); addColumns(findColumnsToAdd()); changeColumnsType(findColumnTypesToChange()); changeColumnsConstraints(); } } /** * Check if the current model add or upgrade an unique or not null column. * @return True if has new unique or not null column. False otherwise. */ private boolean hasNewUniqueOrNotNullColumn() { Collection columnModels = mTableModel.getColumnModels(); for (ColumnModel columnModel : columnModels) { if (columnModel.isIdColumn()) continue; // id don't check unique or nullable, we never upgrade it. ColumnModel columnModelDB = mTableModelDB.getColumnModelByName(columnModel.getColumnName()); if (columnModel.isUnique()) { if (columnModelDB == null || !columnModelDB.isUnique()) { return true; } } if (columnModelDB != null && !columnModel.isNullable() && columnModelDB.isNullable()) { return true; } } return false; } /** * It will find the difference between class model and table model. If * there's a field in the class without a corresponding column in the table, * this field is a new added column. This method find all new added columns. * * @return List with ColumnModel contains information of new columns. */ private List findColumnsToAdd() { List columnsToAdd = new ArrayList<>(); for (ColumnModel columnModel : mTableModel.getColumnModels()) { String columnName = columnModel.getColumnName(); if (!mTableModelDB.containsColumn(columnName)) { // add column action columnsToAdd.add(columnModel); } } return columnsToAdd; } /** * This method helps find the difference between table model from class and * table model from database. Database should always be synchronized with * model class. If there're some fields are removed from class, the table * model from database will be compared to find out which fields are * removed. But there're still some exceptions. The columns named id or _id * won't ever be removed. The foreign key column will be checked some where * else, not from here. * * @return A list with column names need to remove. */ private List findColumnsToRemove() { String tableName = mTableModel.getTableName(); List removeColumns = new ArrayList<>(); Collection columnModels = mTableModelDB.getColumnModels(); for (ColumnModel columnModel : columnModels) { String dbColumnName = columnModel.getColumnName(); if (isNeedToRemove(dbColumnName)) { removeColumns.add(dbColumnName); } } LitePalLog.d(TAG, "remove columns from " + tableName + " >> " + removeColumns); return removeColumns; } /** * It will check each class in the mapping list. Find their types for each * field is changed or not by comparing with the types in table columns. If * there's a column have same name as a field in class but with different * type, then it's a type changed column. * * @return A list contains all ColumnModel which type are changed from database. */ private List findColumnTypesToChange() { List columnsToChangeType = new ArrayList<>(); for (ColumnModel columnModelDB : mTableModelDB.getColumnModels()) { for (ColumnModel columnModel : mTableModel.getColumnModels()) { if (columnModelDB.getColumnName().equalsIgnoreCase(columnModel.getColumnName())) { if (!columnModelDB.getColumnType().equalsIgnoreCase(columnModel.getColumnType())) { if (columnModel.getColumnType().equalsIgnoreCase("blob") && TextUtils.isEmpty(columnModelDB.getColumnType())) { // Case for binary array type upgrade. Do nothing under this condition. } else { // column type is changed columnsToChangeType.add(columnModel); } } if (!hasConstraintChanged) { // for reducing loops, check column constraints change here. LitePalLog.d(TAG, "default value db is:" + columnModelDB.getDefaultValue() + ", default value is:" + columnModel.getDefaultValue()); if (columnModelDB.isNullable() != columnModel.isNullable() || !columnModelDB.getDefaultValue().equalsIgnoreCase(columnModel.getDefaultValue()) || columnModelDB.hasIndex() != columnModel.hasIndex() || (columnModelDB.isUnique() && !columnModel.isUnique())) { // unique constraint can not be added hasConstraintChanged = true; } } } } } return columnsToChangeType; } /** * Tell LitePal the column is need to remove or not. The column can be * remove only on the condition that the following three rules are all * passed. First the corresponding field for this column is removed in the * class. Second this column is not an id column. Third this column is not a * foreign key column. * * @param columnName * The column name to judge * @return Need to remove return true, otherwise return false. */ private boolean isNeedToRemove(String columnName) { return isRemovedFromClass(columnName) && !isIdColumn(columnName) && !isForeignKeyColumn(mTableModel, columnName); } /** * Read a column name from database, and judge the corresponding field in * class is removed or not. * * @param columnName * The column name to judge. * @return If it's removed return true, or return false. */ private boolean isRemovedFromClass(String columnName) { return !mTableModel.containsColumn(columnName); } /** * Generate a SQL for add new column into the existing table. * * @param columnModel * Which contains column info. * @return A SQL to add new column. */ private List generateAddColumnSQLs(ColumnModel columnModel) { List sqls = new ArrayList<>(); sqls.add(generateAddColumnSQL(mTableModel.getTableName(), columnModel)); if (columnModel.hasIndex()) { sqls.add(generateCreateIndexSQL(mTableModel.getTableName(), columnModel)); } return sqls; } /** * This method create a SQL array for the all new columns to add them into * table. * * @param columnModelList * List with ColumnModel to add new column. * @return A SQL list contains add all new columns job. */ private List getAddColumnSQLs(List columnModelList) { List sqls = new ArrayList<>(); for (ColumnModel columnModel : columnModelList) { sqls.addAll(generateAddColumnSQLs(columnModel)); } return sqls; } /** * When some fields are removed from class, the table should synchronize the * changes by removing the corresponding columns. * * @param removeColumnNames * The column names that need to remove. */ private void removeColumns(List removeColumnNames) { LitePalLog.d(TAG, "do removeColumns " + removeColumnNames); removeColumns(removeColumnNames, mTableModel.getTableName()); for (String columnName : removeColumnNames) { mTableModelDB.removeColumnModelByName(columnName); } } /** * When some fields are added into the class after last upgrade, the table * should synchronize the changes by adding the corresponding columns. * * @param columnModelList * List with ColumnModel to add new column. */ private void addColumns(List columnModelList) { LitePalLog.d(TAG, "do addColumn"); execute(getAddColumnSQLs(columnModelList), mDb); for (ColumnModel columnModel : columnModelList) { mTableModelDB.addColumnModel(columnModel); } } /** * When some fields type are changed in class, the table should drop the * before columns and create new columns with same name but new types. * * @param columnModelList * List with ColumnModel to change column type. */ private void changeColumnsType(List columnModelList) { LitePalLog.d(TAG, "do changeColumnsType"); List columnNames = new ArrayList<>(); if (columnModelList != null && !columnModelList.isEmpty()) { for (ColumnModel columnModel : columnModelList) { columnNames.add(columnModel.getColumnName()); } } removeColumns(columnNames); addColumns(columnModelList); } /** * When fields annotation changed in class, table should change the corresponding constraints * make them sync to the fields annotation. */ private void changeColumnsConstraints() { if (hasConstraintChanged) { LitePalLog.d(TAG, "do changeColumnsConstraints"); execute(getChangeColumnsConstraintsSQL(), mDb); } } /** * This method create a SQL array for the whole changing column constraints job. * @return A SQL list contains create temporary table, create new table, add foreign keys, * migrate data and drop temporary table. */ private List getChangeColumnsConstraintsSQL() { String alterToTempTableSQL = generateAlterToTempTableSQL(mTableModel.getTableName()); String createNewTableSQL = generateCreateTableSQL(mTableModel); List addForeignKeySQLs = generateAddForeignKeySQL(); String dataMigrationSQL = generateDataMigrationSQL(mTableModelDB); String dropTempTableSQL = generateDropTempTableSQL(mTableModel.getTableName()); List createIndexSQLs = generateCreateIndexSQLs(mTableModel); List sqls = new ArrayList<>(); sqls.add(alterToTempTableSQL); sqls.add(createNewTableSQL); sqls.addAll(addForeignKeySQLs); sqls.add(dataMigrationSQL); sqls.add(dropTempTableSQL); sqls.addAll(createIndexSQLs); LitePalLog.d(TAG, "generateChangeConstraintSQL >> "); for (String sql : sqls) { LitePalLog.d(TAG, sql); } LitePalLog.d(TAG, "<< generateChangeConstraintSQL"); return sqls; } /** * Generate a SQL List for adding foreign keys. Changing constraints job should remain all the * existing columns including foreign keys. This method add origin foreign keys after creating * table. * @return A SQL List for adding foreign keys. */ private List generateAddForeignKeySQL() { List addForeignKeySQLs = new ArrayList<>(); List foreignKeyColumns = getForeignKeyColumns(mTableModel); for (String foreignKeyColumn : foreignKeyColumns) { if (!mTableModel.containsColumn(foreignKeyColumn)) { ColumnModel columnModel = new ColumnModel(); columnModel.setColumnName(foreignKeyColumn); columnModel.setColumnType("integer"); addForeignKeySQLs.add(generateAddColumnSQL(mTableModel.getTableName(), columnModel)); } } return addForeignKeySQLs; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/callback/DatabaseListener.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.callback; /** * Callback for listening database create and upgrade events. * @author Tony Green * @since 2.0 */ public interface DatabaseListener { void onCreate(); void onUpgrade(int oldVersion, int newVersion); } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/model/AssociationsModel.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.model; import org.litepal.util.Const; /** * This is a model class for table associations. It stores table name, * associated table name, table name which holds foreign key, and association * type. Relations have three types. One2One, Many2One and Many2Many. If the * association type is One2One or Many2One, the foreign key will be on the side * of tableHoldsForeignKey. If the association is Many2Many, a intermediate join * table will be built and named by the concatenation of the two target table * names in alphabetical order with underline in the middle. * * @author Tony Green * @since 1.0 */ public class AssociationsModel { /** * Table name. */ private String tableName; /** * Associated table name. */ private String associatedTableName; /** * The table which holds foreign key. */ private String tableHoldsForeignKey; /** * The association type, including {@link Const.Model#MANY_TO_ONE}, * {@link Const.Model#MANY_TO_MANY}, {@link Const.Model#ONE_TO_ONE}. */ private int associationType; /** * Get table name. * * @return Return the table name. */ public String getTableName() { return tableName; } /** * Set table name. * * @param tableName * The table name to set. */ public void setTableName(String tableName) { this.tableName = tableName; } /** * Get associated table name. * * @return Return the associated table name. */ public String getAssociatedTableName() { return associatedTableName; } /** * Set associated table name. * * @param associatedTableName * The associated table name. */ public void setAssociatedTableName(String associatedTableName) { this.associatedTableName = associatedTableName; } /** * Get the table which holds foreign key. * * @return The table which holds foreign key. */ public String getTableHoldsForeignKey() { return tableHoldsForeignKey; } /** * Set the table which holds foreign key. * * @param tableHoldsForeignKey * The table which holds foreign key to set. */ public void setTableHoldsForeignKey(String tableHoldsForeignKey) { this.tableHoldsForeignKey = tableHoldsForeignKey; } /** * Get the association type. * * @return The association type. */ public int getAssociationType() { return associationType; } /** * Set the association type. * * @param associationType * Within {@link Const.Model#MANY_TO_ONE}, * {@link Const.Model#MANY_TO_MANY}, * {@link Const.Model#ONE_TO_ONE}. */ public void setAssociationType(int associationType) { this.associationType = associationType; } /** * Override equals method to make sure that if two associated tables in the * association model are same ignoring sides, they are same association * model. */ @Override public boolean equals(Object o) { if (o instanceof AssociationsModel) { AssociationsModel association = (AssociationsModel) o; if (association.getTableName() != null && association.getAssociatedTableName() != null) { if (association.getAssociationType() == associationType && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) { if (association.getTableName().equals(tableName) && association.getAssociatedTableName().equals(associatedTableName) && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) { return true; } else if (association.getTableName().equals(associatedTableName) && association.getAssociatedTableName().equals(tableName) && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) { return true; } } } } return false; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/model/ColumnModel.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.model; import android.text.TextUtils; /** * This is a model class for columns. It stores column name, column type, and column constraints * information. * @author Tony Green * @since 1.3 */ public class ColumnModel { /** * Name of column. */ private String columnName; /** * Type for column. */ private String columnType; /** * Nullable constraint. */ private boolean isNullable = true; /** * Unique constraint. */ private boolean isUnique = false; /** * Default constraint. */ private String defaultValue = ""; /** * Has index for this column or not. */ private boolean hasIndex = false; public String getColumnName() { return columnName; } public void setColumnName(String columnName) { this.columnName = columnName; } public String getColumnType() { return columnType; } public void setColumnType(String columnType) { this.columnType = columnType; } public boolean isNullable() { return isNullable; } public void setNullable(boolean isNullable) { this.isNullable = isNullable; } public boolean isUnique() { return isUnique; } public void setUnique(boolean isUnique) { this.isUnique = isUnique; } public String getDefaultValue() { return defaultValue; } public boolean hasIndex() { return hasIndex; } public void setHasIndex(boolean hasIndex) { this.hasIndex = hasIndex; } public void setDefaultValue(String defaultValue) { if ("text".equalsIgnoreCase(columnType)) { if (!TextUtils.isEmpty(defaultValue)) { this.defaultValue = "'" + defaultValue + "'"; } } else { this.defaultValue = defaultValue; } } /** * Judge current ColumnModel is id column or not. * @return True if it's id column. False otherwise. */ public boolean isIdColumn() { return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName); } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/model/GenericModel.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.model; /** * This is a model class for generic table. It stores table name, value column name, value column * type and value id column name. This class is used to create generic tables when generic collection * fields are declared in the model class. * * @author Tony Green * @since 1.4 */ public class GenericModel { /** * Table name. */ private String tableName; /** * Column name for storing value. */ private String valueColumnName; /** * Column type for storing value. */ private String valueColumnType; /** * Column name for reference with main table. */ private String valueIdColumnName; /** * Only used when query generic data. This is cache fields for improving performance. */ private String getMethodName; public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getValueColumnName() { return valueColumnName; } public void setValueColumnName(String valueColumnName) { this.valueColumnName = valueColumnName; } public String getValueColumnType() { return valueColumnType; } public void setValueColumnType(String valueColumnType) { this.valueColumnType = valueColumnType; } public String getValueIdColumnName() { return valueIdColumnName; } public void setValueIdColumnName(String valueIdColumnName) { this.valueIdColumnName = valueIdColumnName; } public String getGetMethodName() { return getMethodName; } public void setGetMethodName(String getMethodName) { this.getMethodName = getMethodName; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/model/TableModel.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.model; import org.litepal.util.BaseUtility; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * This is a model class for tables. It stores a table name and a HashMap for * columns in the table. * * @author Tony Green * @since 1.0 */ public class TableModel { /** * Table name. */ private String tableName; /** * A map contains all column models with column name, type and constraints. */ private Map columnModelMap = new HashMap<>(); /** * Class name for the table name. This value might be null. Don't rely on it. */ private String className; /** * Get table name. * * @return Name of table. */ public String getTableName() { return tableName; } /** * Set table name. * * @param tableName * Name of table. */ public void setTableName(String tableName) { this.tableName = tableName; } /** * Get class name. * * @return Return the class name or null. */ public String getClassName() { return className; } /** * Set class name. * * @param className * The class name. */ public void setClassName(String className) { this.className = className; } /** * Add a column model into the table model. * * @param columnModel * A column model contains name, type and constraints. */ public void addColumnModel(ColumnModel columnModel) { columnModelMap.put(BaseUtility.changeCase(columnModel.getColumnName()), columnModel); } /** * Find all the column models of the current table model. * @return A list contains all column models. */ public Collection getColumnModels() { return columnModelMap.values(); } /** * Find the ColumnModel which can map the column name passed in. * @param columnName * Name of column. * @return A ColumnModel which can map the column name passed in. Or null. */ public ColumnModel getColumnModelByName(String columnName) { return columnModelMap.get(BaseUtility.changeCase(columnName)); } /** * Remove a column model by the specified column name. * @param columnName * Name of the column to remove. */ public void removeColumnModelByName(String columnName) { columnModelMap.remove(BaseUtility.changeCase(columnName)); } /** * Judge the table model has such a column or not. * @param columnName * The name of column to check. * @return True if matches a column in the table model. False otherwise. */ public boolean containsColumn(String columnName) { return columnModelMap.containsKey(BaseUtility.changeCase(columnName)); } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/BlobOrm.java ================================================ package org.litepal.tablemanager.typechange; /** * This class deals with byte type. * * @author Tony Green * @since 1.3.1 */ public class BlobOrm extends OrmChange{ /** * If the field type passed in is byte, it will change it into blob as * column type. */ @Override public String object2Relation(String fieldType) { if (fieldType != null) { if (fieldType.equals("[B")) { return "blob"; } } return null; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/BooleanOrm.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.typechange; /** * This class deals with boolean type. * * @author Tony Green * @since 1.0 */ public class BooleanOrm extends OrmChange { /** * If the field type passed in is boolean, it will change it into integer as * column type. */ @Override public String object2Relation(String fieldType) { if (fieldType != null) { if (fieldType.equals("boolean") || fieldType.equals("java.lang.Boolean")) { return "integer"; } } return null; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/DateOrm.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.typechange; /** * This class deals with date type. * * @author Tony Green * @since 1.1 */ public class DateOrm extends OrmChange { /** * If the field type passed in is Date, it will change it into integer as * column type. */ @Override public String object2Relation(String fieldType) { if (fieldType != null) { if (fieldType.equals("java.util.Date")) { return "integer"; } } return null; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/DecimalOrm.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.typechange; /** * This class deals with decimal type. * * @author Tony Green * @since 1.0 */ public class DecimalOrm extends OrmChange { /** * If the field type passed in is float or double, it will change it into * real as column type. */ @Override public String object2Relation(String fieldType) { if (fieldType != null) { if (fieldType.equals("float") || fieldType.equals("java.lang.Float")) { return "real"; } if (fieldType.equals("double") || fieldType.equals("java.lang.Double")) { return "real"; } } return null; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/NumericOrm.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.typechange; /** * This class deals with numeric type. * * @author Tony Green * @since 1.0 */ public class NumericOrm extends OrmChange { /** * If the field type passed in is int, long or short, it will change it into * integer as column type. */ @Override public String object2Relation(String fieldType) { if (fieldType != null) { if (fieldType.equals("int") || fieldType.equals("java.lang.Integer")) { return "integer"; } if (fieldType.equals("long") || fieldType.equals("java.lang.Long")) { return "integer"; } if (fieldType.equals("short") || fieldType.equals("java.lang.Short")) { return "integer"; } } return null; } } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/OrmChange.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.typechange; /** * This is abstract super class to map the object field types to database column * types. The purpose of this class is to define a abstract method, and let all * subclasses implement it. Each subclass deals with a kind of changing and each * subclass will do their own logic to finish the changing job. * * @author Tony Green * @since 1.0 */ public abstract class OrmChange { /** * Subclasses implement this method to do their own logic to change types. * * @param fieldType * The field type passed in. * @return Column type. */ public abstract String object2Relation(String fieldType); } ================================================ FILE: core/src/main/java/org/litepal/tablemanager/typechange/TextOrm.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.tablemanager.typechange; /** * This class deals with text type. * * @author Tony Green * @since 1.0 */ public class TextOrm extends OrmChange { /** * If the field type passed in is char or String, it will change it into * text as column type. */ @Override public String object2Relation(String fieldType) { if (fieldType != null) { if (fieldType.equals("char") || fieldType.equals("java.lang.Character")) { return "text"; } if (fieldType.equals("java.lang.String")) { return "text"; } } return null; } } ================================================ FILE: core/src/main/java/org/litepal/util/BaseUtility.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util; import android.content.res.AssetManager; import android.text.TextUtils; import org.litepal.LitePalApplication; import org.litepal.exceptions.LitePalSupportException; import org.litepal.parser.LitePalAttr; import java.io.IOException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Locale; /** * A utility class to help LitePal with some base actions that might through any * components. These actions can help classes just do the jobs they care, and * help them out of the trivial work. * * @author Tony Green * @since 1.0 */ public class BaseUtility { /** * Disable to create an instance of BaseUtility. */ private BaseUtility() { } /** * It will change the case of the passing parameter into the case defined in * litepal.xml file. * * @param string * The string want to change case. * @return The string after changing case. If the name is null, then simply * return null. */ public static String changeCase(String string) { if (string != null) { LitePalAttr litePalAttr = LitePalAttr.getInstance(); String cases = litePalAttr.getCases(); if (Const.Config.CASES_KEEP.equals(cases)) { return string; } else if (Const.Config.CASES_UPPER.equals(cases)) { return string.toUpperCase(Locale.US); } return string.toLowerCase(Locale.US); } return null; } /** * This helper method makes up the shortage of contains method in Collection * to support the function of case insensitive contains. It only supports * the String generic type of collection, cause other types have no cases * concept. * * @param collection * The collection contains string data. * @param string * The string want to look for in the collection. * @return If the string is in the collection without case concern return * true, otherwise return false. If the collection is null, return * false. */ public static boolean containsIgnoreCases(Collection collection, String string) { if (collection == null) { return false; } if (string == null) { return collection.contains(null); } boolean contains = false; for (String element : collection) { if (string.equalsIgnoreCase(element)) { contains = true; break; } } return contains; } /** * Capitalize make the first letter of the word be upper case. * * @param string * The word to capitalize. * @return The word after capitalize. */ public static String capitalize(String string) { if (!TextUtils.isEmpty(string)) { return string.substring(0, 1).toUpperCase(Locale.US) + string.substring(1); } return string == null ? null : ""; } /** * Count how many marks existed in string. * * @param string * The source sentence. * @param mark * The specific substring to count. * @return The number of marks existed in string. */ public static int count(String string, String mark) { if (!TextUtils.isEmpty(string) && !TextUtils.isEmpty(mark)) { int count = 0; int index = string.indexOf(mark); while (index != -1) { count++; string = string.substring(index + mark.length()); index = string.indexOf(mark); } return count; } return 0; } /** * Check the number of question mark existed in conditions[0] equals the * number of rest conditions elements or not. If not equals, throws * LitePalSupportException. * * @param conditions * A string array representing the WHERE part of an SQL * statement. * @throws LitePalSupportException */ public static void checkConditionsCorrect(String... conditions) { if (conditions != null) { int conditionsSize = conditions.length; if (conditionsSize > 0) { String whereClause = conditions[0]; int placeHolderSize = BaseUtility.count(whereClause, "?"); if (conditionsSize != placeHolderSize + 1) { throw new LitePalSupportException(LitePalSupportException.UPDATE_CONDITIONS_EXCEPTION); } } } } /** * Judge a field type is supported or not. Currently only basic data types * and String are supported. * * @param fieldType * Type of the field. * @return Supported return true, not supported return false. */ public static boolean isFieldTypeSupported(String fieldType) { if ("boolean".equals(fieldType) || "java.lang.Boolean".equals(fieldType)) { return true; } if ("float".equals(fieldType) || "java.lang.Float".equals(fieldType)) { return true; } if ("double".equals(fieldType) || "java.lang.Double".equals(fieldType)) { return true; } if ("int".equals(fieldType) || "java.lang.Integer".equals(fieldType)) { return true; } if ("long".equals(fieldType) || "java.lang.Long".equals(fieldType)) { return true; } if ("short".equals(fieldType) || "java.lang.Short".equals(fieldType)) { return true; } if ("char".equals(fieldType) || "java.lang.Character".equals(fieldType)) { return true; } if ("java.lang.String".equals(fieldType) || "java.util.Date".equals(fieldType)) { return true; } return false; } /** * Judge a generic type is supported or not. Currently only basic data types * and String are supported. * * @param genericType * Type of the generic field. * @return Supported return true, not supported return false. */ public static boolean isGenericTypeSupported(String genericType) { if ("java.lang.String".equals(genericType)) { return true; } else if ("java.lang.Integer".equals(genericType)) { return true; } else if ("java.lang.Float".equals(genericType)) { return true; } else if ("java.lang.Double".equals(genericType)) { return true; } else if ("java.lang.Long".equals(genericType)) { return true; } else if ("java.lang.Short".equals(genericType)) { return true; } else if ("java.lang.Boolean".equals(genericType)) { return true; } else if ("java.lang.Character".equals(genericType)) { return true; } return false; } /** * If the litepal.xml configuration file exists. * @return True if exists, false otherwise. */ public static boolean isLitePalXMLExists() { try { AssetManager assetManager = LitePalApplication.getContext().getAssets(); String[] fileNames = assetManager.list(""); if (fileNames != null && fileNames.length > 0) { for (String fileName : fileNames) { if (Const.Config.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) { return true; } } } } catch (IOException e) { } return false; } /** * Check the existence of the specific class and method. * * @param className * Class name with full package name. * @param methodName * Method name. * @return Return true if both of class and method are exist. Otherwise return false. */ public static boolean isClassAndMethodExist(String className, String methodName) { try { Class clazz = Class.forName(className); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (methodName.equals(method.getName())) { return true; } } } catch (Exception e) { e.printStackTrace(); } return false; } } ================================================ FILE: core/src/main/java/org/litepal/util/Const.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util; public interface Const { public interface Model { /** * One2One constant value. */ public static final int ONE_TO_ONE = 1; /** * Many2One constant value. */ public static final int MANY_TO_ONE = 2; /** * Many2Many constant value. */ public static final int MANY_TO_MANY = 3; } public interface Config { /** * The suffix for each database file. */ public static final String DB_NAME_SUFFIX = ".db"; /** * Constant for upper case. */ public static final String CASES_UPPER = "upper"; /** * Constant for lower case. */ public static final String CASES_LOWER = "lower"; /** * Constant for keep case. */ public static final String CASES_KEEP = "keep"; /** * Constant configuration file name. */ public static final String CONFIGURATION_FILE_NAME = "litepal.xml"; } public interface TableSchema { /** * Table name in database. */ public static final String TABLE_NAME = "table_schema"; /** * The name column in table_schema. */ public static final String COLUMN_NAME = "name"; /** * The type column in table_schema. */ public static final String COLUMN_TYPE = "type"; /** * Constant for normal table. */ public static final int NORMAL_TABLE = 0; /** * Constant for intermediate join table. */ public static final int INTERMEDIATE_JOIN_TABLE = 1; /** * Constant for generic table. */ public static final int GENERIC_TABLE = 2; } } ================================================ FILE: core/src/main/java/org/litepal/util/DBUtility.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import android.util.Pair; import org.litepal.exceptions.DatabaseGenerateException; import org.litepal.tablemanager.model.ColumnModel; import org.litepal.tablemanager.model.TableModel; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A utility class to help LitePal with some database actions. These actions can * help classes just do the jobs they care, and help them out of the trivial * work. * * @author Tony * @since 1.0 */ public class DBUtility { private static final String TAG = "DBUtility"; private static final String SQLITE_KEYWORDS = ",abort,add,after,all,alter,and,as,asc,autoincrement,before,begin,between,by,cascade,check,collate,column,commit,conflict,constraint,create,cross,database,deferrable,deferred,delete,desc,distinct,drop,each,end,escape,except,exclusive,exists,foreign,from,glob,group,having,in,index,inner,insert,intersect,into,is,isnull,join,like,limit,match,natural,not,notnull,null,of,offset,on,or,order,outer,plan,pragma,primary,query,raise,references,regexp,reindex,release,rename,replace,restrict,right,rollback,row,savepoint,select,set,table,temp,temporary,then,to,transaction,trigger,union,unique,update,using,vacuum,values,view,virtual,when,where,"; private static final String KEYWORDS_COLUMN_SUFFIX = "_lpcolumn"; private static final String REG_OPERATOR = "\\s*(=|!=|<>|<|>)"; private static final String REG_FUZZY = "\\s+(not\\s+)?(like|between)\\s+"; private static final String REG_COLLECTION = "\\s+(not\\s+)?(in)\\s*\\("; /** * Disable to create an instance of DBUtility. */ private DBUtility() { } /** * Get the corresponding table name by the full class name with package. It * will only get the short class name without package name as table name. * * @param className * Full class name with package. * @return Return the table name by getting the short class name. Return * null if the class name is null or invalid. * */ public static String getTableNameByClassName(String className) { if (!TextUtils.isEmpty(className)) { if ('.' == className.charAt(className.length() - 1)) { return null; } else { return className.substring(className.lastIndexOf(".") + 1); } } return null; } /** * Get the index name by column name. * In LitePal index name always named like columnName_index. * @param tableName * Table name. * @param columnName * Column name. * @return Index name or null if column name is null or empty. */ public static String getIndexName(String tableName, String columnName) { if (!TextUtils.isEmpty(tableName) && !TextUtils.isEmpty(columnName)) { return tableName + "_" + columnName + "_index"; } return null; } /** * Get the corresponding table name list by the full class name list with * package. Each table name will only get the short class name without * package. * * @param classNames * The list of full class name with package. * @return Return the table name list. */ public static List getTableNameListByClassNameList(List classNames) { List tableNames = new ArrayList(); if (classNames != null && !classNames.isEmpty()) { for (String className : classNames) { tableNames.add(getTableNameByClassName(className)); } } return tableNames; } /** * Get table name by the given foreign column name. * * @param foreignColumnName * The foreign column name. Standard pattern is tablename_id. * @return The table name. Return null if the given foreign column is null, * empty or invalid. */ public static String getTableNameByForeignColumn(String foreignColumnName) { if (!TextUtils.isEmpty(foreignColumnName)) { if (foreignColumnName.toLowerCase(Locale.US).endsWith("_id")) { return foreignColumnName.substring(0, foreignColumnName.length() - "_id".length()); } return null; } return null; } /** * Create intermediate join table name by the concatenation of the two * target table names in alphabetical order with underline in the middle. * * @param tableName * First table name. * @param associatedTableName * The associated table name. * @return The table name by the concatenation of the two target table names * in alphabetical order with underline in the middle. If the table * name or associated table name is null of empty, return null. */ public static String getIntermediateTableName(String tableName, String associatedTableName) { if (!(TextUtils.isEmpty(tableName) || TextUtils.isEmpty(associatedTableName))) { String intermediateTableName; if (tableName.toLowerCase(Locale.US).compareTo(associatedTableName.toLowerCase(Locale.US)) <= 0) { intermediateTableName = tableName + "_" + associatedTableName; } else { intermediateTableName = associatedTableName + "_" + tableName; } return intermediateTableName; } return null; } /** * Create generic table name by the concatenation of the class model's table name and simple * generic type name with underline in the middle. * @param className * Name of the class model. * @param fieldName * Name of the generic type field. * @return Table name by the concatenation of the class model's table name and simple * generic type name with underline in the middle. */ public static String getGenericTableName(String className, String fieldName) { String tableName = getTableNameByClassName(className); return BaseUtility.changeCase(tableName + "_" + fieldName); } /** * The column name for referenced id in generic table. * @param className * Name of the class model. * @return The column name for referenced id in generic table. */ public static String getGenericValueIdColumnName(String className) { return BaseUtility.changeCase(getTableNameByClassName(className) + "_id"); } public static String getM2MSelfRefColumnName(Field field) { return BaseUtility.changeCase(field.getName() + "_id"); } /** * Judge the table name is an intermediate table or not. * * @param tableName * Table name in database. * @return Return true if the table name is an intermediate table. Otherwise * return false. */ public static boolean isIntermediateTable(String tableName, SQLiteDatabase db) { if (!TextUtils.isEmpty(tableName)) { if (tableName.matches("[0-9a-zA-Z]+_[0-9a-zA-Z]+")) { Cursor cursor = null; try { cursor = db.query(Const.TableSchema.TABLE_NAME, null, null, null, null, null, null); if (cursor.moveToFirst()) { do { String tableNameDB = cursor.getString(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); if (tableName.equalsIgnoreCase(tableNameDB)) { int tableType = cursor.getInt(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_TYPE)); if (tableType == Const.TableSchema.INTERMEDIATE_JOIN_TABLE) { return true; } break; } } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } } return false; } /** * Judge the table name is an generic table or not. * * @param tableName * Table name in database. * @return Return true if the table name is an generic table. Otherwise * return false. */ public static boolean isGenericTable(String tableName, SQLiteDatabase db) { if (!TextUtils.isEmpty(tableName)) { if (tableName.matches("[0-9a-zA-Z]+_[0-9a-zA-Z]+")) { Cursor cursor = null; try { cursor = db.query(Const.TableSchema.TABLE_NAME, null, null, null, null, null, null); if (cursor.moveToFirst()) { do { String tableNameDB = cursor.getString(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); if (tableName.equalsIgnoreCase(tableNameDB)) { int tableType = cursor.getInt(cursor .getColumnIndexOrThrow(Const.TableSchema.COLUMN_TYPE)); if (tableType == Const.TableSchema.GENERIC_TABLE) { return true; } break; } } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } } return false; } /** * Test if the table name passed in exists in the database. Cases are * ignored. * * @param tableName * The table name. * @return Return true if there's already a same name table exist, otherwise * return false. */ public static boolean isTableExists(String tableName, SQLiteDatabase db) { boolean exist; try { exist = BaseUtility.containsIgnoreCases(findAllTableNames(db), tableName); } catch (Exception e) { e.printStackTrace(); exist = false; } return exist; } /** * Test if a column exists in a table. Cases are ignored. * * @param columnName * The column name. * @param tableName * The table name. * @param db * Instance of SQLiteDatabase. * @return If there's a column named as the column name passed in, return * true. Or return false. If any of the passed in parameters is null * or empty, return false. */ public static boolean isColumnExists(String columnName, String tableName, SQLiteDatabase db) { if (TextUtils.isEmpty(columnName) || TextUtils.isEmpty(tableName)) { return false; } boolean exist = false; Cursor cursor = null; try { String checkingColumnSQL = "pragma table_info(" + tableName + ")"; cursor = db.rawQuery(checkingColumnSQL, null); if (cursor.moveToFirst()) { do { String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); if (columnName.equalsIgnoreCase(name)) { exist = true; break; } } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); exist = false; } finally { if (cursor != null) { cursor.close(); } } return exist; } /** * Find all table names in the database. If there's some wrong happens when * finding tables, it will throw exceptions. * * @param db * Instance of SQLiteDatabase. * @return A list with all table names. * @throws org.litepal.exceptions.DatabaseGenerateException */ public static List findAllTableNames(SQLiteDatabase db) { List tableNames = new ArrayList(); Cursor cursor = null; try { cursor = db.rawQuery("select * from sqlite_master where type = ?", new String[] { "table" }); if (cursor.moveToFirst()) { do { String tableName = cursor.getString(cursor.getColumnIndexOrThrow("tbl_name")); if (!tableNames.contains(tableName)) { tableNames.add(tableName); } } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); throw new DatabaseGenerateException(e.getMessage()); } finally { if (cursor != null) { cursor.close(); } } return tableNames; } /** * Look from the database to find a table named same as the table name in * table model. Then iterate the columns and types of this table to create a * new instance of table model. If there's no such a table in the database, * then throw DatabaseGenerateException. * * @param tableName * Table name. * @param db * Instance of SQLiteDatabase. * @return A table model object with values from database table. * @throws org.litepal.exceptions.DatabaseGenerateException */ public static TableModel findPragmaTableInfo(String tableName, SQLiteDatabase db) { if (isTableExists(tableName, db)) { Pair, Set> indexPair = findIndexedColumns(tableName, db); Set indexColumns = indexPair.first; Set uniqueColumns = indexPair.second; TableModel tableModelDB = new TableModel(); tableModelDB.setTableName(tableName); String checkingColumnSQL = "pragma table_info(" + tableName + ")"; Cursor cursor = null; try { cursor = db.rawQuery(checkingColumnSQL, null); if (cursor.moveToFirst()) { do { ColumnModel columnModel = new ColumnModel(); String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); String type = cursor.getString(cursor.getColumnIndexOrThrow("type")); boolean nullable = cursor.getInt(cursor.getColumnIndexOrThrow("notnull")) != 1; boolean unique = uniqueColumns.contains(name); boolean hasIndex = indexColumns.contains(name); String defaultValue = cursor.getString(cursor.getColumnIndexOrThrow("dflt_value")); columnModel.setColumnName(name); columnModel.setColumnType(type); columnModel.setNullable(nullable); columnModel.setUnique(unique); columnModel.setHasIndex(hasIndex); if (defaultValue != null) { defaultValue = defaultValue.replace("'", ""); } else { defaultValue = ""; } columnModel.setDefaultValue(defaultValue); tableModelDB.addColumnModel(columnModel); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); throw new DatabaseGenerateException(e.getMessage()); } finally { if (cursor != null) { cursor.close(); } } return tableModelDB; } else { throw new DatabaseGenerateException( DatabaseGenerateException.TABLE_DOES_NOT_EXIST_WHEN_EXECUTING + tableName); } } /** * Find all columns with index, including normal index and unique index of specified table. * @param tableName * The table to find unique columns. * @param db * Instance of SQLiteDatabase. * @return A pair contains two types of index columns. First is normal index columns. Second is unique index columns. */ public static Pair, Set> findIndexedColumns(String tableName, SQLiteDatabase db) { Set indexColumns = new HashSet<>(); Set uniqueColumns = new HashSet<>(); Cursor cursor = null; Cursor innerCursor = null; try { cursor = db.rawQuery("pragma index_list(" + tableName +")", null); if (cursor.moveToFirst()) { do { boolean unique = cursor.getInt(cursor.getColumnIndexOrThrow("unique")) == 1; String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); innerCursor = db.rawQuery("pragma index_info(" + name + ")", null); if (innerCursor.moveToFirst()) { String columnName = innerCursor.getString(innerCursor.getColumnIndexOrThrow("name")); if (unique) { uniqueColumns.add(columnName); } else { indexColumns.add(columnName); } } } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); throw new DatabaseGenerateException(e.getMessage()); } finally { if (cursor != null) { cursor.close(); } if (innerCursor != null) { innerCursor.close(); } } return new Pair<>(indexColumns, uniqueColumns); } /** * If the field name is conflicted with SQLite keywords. Return true if conflicted, return false * otherwise. * @param fieldName * Name of the field. * @return True if conflicted, false otherwise. */ public static boolean isFieldNameConflictWithSQLiteKeywords(String fieldName) { if (!TextUtils.isEmpty(fieldName)) { String fieldNameWithComma = "," + fieldName.toLowerCase(Locale.US) + ","; return SQLITE_KEYWORDS.contains(fieldNameWithComma); } return false; } /** * Convert the passed in name to valid column name if the name is conflicted with SQLite keywords. * The convert rule is to append {@link #KEYWORDS_COLUMN_SUFFIX} to the name as new column name. * @param columnName * Original column name. * @return Converted name as new column name if conflicted with SQLite keywords. */ public static String convertToValidColumnName(String columnName) { if (isFieldNameConflictWithSQLiteKeywords(columnName)) { return columnName + KEYWORDS_COLUMN_SUFFIX; } return columnName; } /** * Convert the where clause if it contains invalid column names which conflict with SQLite keywords. * @param whereClause * where clause for query, update or delete. * @return Converted where clause with valid column names. */ public static String convertWhereClauseToColumnName(String whereClause) { if (!TextUtils.isEmpty(whereClause)) { try { StringBuffer convertedWhereClause = new StringBuffer(); Pattern p = Pattern.compile("(\\w+" + REG_OPERATOR + "|\\w+" + REG_FUZZY + "|\\w+" + REG_COLLECTION + ")"); Matcher m = p.matcher(whereClause); while (m.find()) { String matches = m.group(); String column = matches.replaceAll("(" + REG_OPERATOR + "|" + REG_FUZZY + "|" + REG_COLLECTION + ")", ""); String rest = matches.replace(column, ""); column = convertToValidColumnName(column); m.appendReplacement(convertedWhereClause, column + rest); } m.appendTail(convertedWhereClause); return convertedWhereClause.toString(); } catch (Exception e) { e.printStackTrace(); } } return whereClause; } /** * Convert the select clause if it contains invalid column names which conflict with SQLite keywords. * @param columns * A String array of which columns to return. Passing null will * return all columns. * @return Converted select clause with valid column names. */ public static String[] convertSelectClauseToValidNames(String[] columns) { if (columns != null && columns.length > 0) { String[] convertedColumns = new String[columns.length]; for (int i = 0; i < columns.length; i++) { convertedColumns[i] = convertToValidColumnName(columns[i]); } return convertedColumns; } return null; } /** * Convert the order by clause if it contains invalid column names which conflict with SQLite keywords. * @param orderBy * How to order the rows, formatted as an SQL ORDER BY clause. Passing null will use * the default sort order, which may be unordered. * @return Converted order by clause with valid column names. */ public static String convertOrderByClauseToValidName(String orderBy) { if (!TextUtils.isEmpty(orderBy)) { orderBy = orderBy.trim().toLowerCase(Locale.US); if (orderBy.contains(",")) { String[] orderByItems = orderBy.split(","); StringBuilder builder = new StringBuilder(); boolean needComma = false; for (String orderByItem : orderByItems) { if (needComma) { builder.append(","); } builder.append(convertOrderByItem(orderByItem)); needComma = true; } orderBy = builder.toString(); } else { orderBy = convertOrderByItem(orderBy); } return orderBy; } return null; } /** * Convert the order by item if it is invalid column name which conflict with SQLite keywords. * @param orderByItem * The single order by condition. * @return Converted order by item with valid column name. */ private static String convertOrderByItem(String orderByItem) { String column; String append; if (orderByItem.endsWith("asc")) { column = orderByItem.replace("asc", "").trim(); append = " asc"; } else if (orderByItem.endsWith("desc")) { column = orderByItem.replace("desc", "").trim(); append = " desc"; } else { column = orderByItem; append = ""; } return convertToValidColumnName(column) + append; } } ================================================ FILE: core/src/main/java/org/litepal/util/LitePalLog.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util; import android.util.Log; public final class LitePalLog { public static final int DEBUG = 2; public static final int ERROR = 5; public static int level = ERROR; public static void d(String tagName, String message) { if (level <= DEBUG) { Log.d(tagName, message); } } public static void e(String tagName, Exception e){ if (level <= ERROR) { Log.e(tagName, e.getMessage(), e); } } } ================================================ FILE: core/src/main/java/org/litepal/util/SharedUtil.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util; import org.litepal.LitePalApplication; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; /** * LitePal used shared preferences a lot for storing versions and a lot of other * necessary values. This utility helps LitePal save and read each key-value * pairs from shared preferences file. * * @author Tony Green * @since 1.0 */ public class SharedUtil { private static final String VERSION = "litepal_version"; private static final String LITEPAL_PREPS = "litepal_prefs"; /** * Each time database upgrade, the version of database stored in shared * preference will update. * @param extraKeyName * Pass the name of the using database usually. Pass null if it's default database. * @param newVersion * new version of database */ public static void updateVersion(String extraKeyName, int newVersion) { SharedPreferences.Editor sEditor = LitePalApplication.getContext() .getSharedPreferences(LITEPAL_PREPS, Context.MODE_PRIVATE).edit(); if (TextUtils.isEmpty(extraKeyName)) { sEditor.putInt(VERSION, newVersion); } else { if (extraKeyName.endsWith(Const.Config.DB_NAME_SUFFIX)) { extraKeyName = extraKeyName.replace(Const.Config.DB_NAME_SUFFIX, ""); } sEditor.putInt(VERSION + "_" + extraKeyName, newVersion); } sEditor.apply(); } /** * Get the last database version. * @param extraKeyName * Pass the name of the using database usually. Pass null if it's default database. * @return the last database version */ public static int getLastVersion(String extraKeyName) { SharedPreferences sPref = LitePalApplication.getContext().getSharedPreferences( LITEPAL_PREPS, Context.MODE_PRIVATE); if (TextUtils.isEmpty(extraKeyName)) { return sPref.getInt(VERSION, 0); } else { if (extraKeyName.endsWith(Const.Config.DB_NAME_SUFFIX)) { extraKeyName = extraKeyName.replace(Const.Config.DB_NAME_SUFFIX, ""); } return sPref.getInt(VERSION + "_" + extraKeyName, 0); } } /** * Remove the version with specified extra key name. * @param extraKeyName * Pass the name of the using database usually. Pass null if it's default database. */ public static void removeVersion(String extraKeyName) { SharedPreferences.Editor sEditor = LitePalApplication.getContext() .getSharedPreferences(LITEPAL_PREPS, Context.MODE_PRIVATE).edit(); if (TextUtils.isEmpty(extraKeyName)) { sEditor.remove(VERSION); } else { if (extraKeyName.endsWith(Const.Config.DB_NAME_SUFFIX)) { extraKeyName = extraKeyName.replace(Const.Config.DB_NAME_SUFFIX, ""); } sEditor.remove(VERSION + "_" + extraKeyName); } sEditor.apply(); } } ================================================ FILE: core/src/main/java/org/litepal/util/cipher/AESCrypt.java ================================================ /* * Copyright (c) 2014 Scott Alexander-Bown * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util.cipher; import android.util.Base64; import android.util.Log; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * Encrypt and decrypt messages using AES 256 bit encryption that are compatible with AESCrypt-ObjC and AESCrypt Ruby. *

* Created by scottab on 04/10/2014. */ public final class AESCrypt { private static final String TAG = "AESCrypt"; //AESCrypt-ObjC uses CBC and PKCS7Padding private static final String AES_MODE = "AES/CBC/PKCS7Padding"; private static final String CHARSET = "UTF-8"; //AESCrypt-ObjC uses SHA-256 (and so a 256-bit key) private static final String HASH_ALGORITHM = "SHA-256"; //AESCrypt-ObjC uses blank IV (not the best security, but the aim here is compatibility) private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //togglable log option (please turn off in live!) public static boolean DEBUG_LOG_ENABLED = false; /** * Generates SHA256 hash of the password which is used as key * * @param password used to generated key * @return SHA256 of the password */ private static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException { final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); byte[] bytes = password.getBytes("UTF-8"); digest.update(bytes, 0, bytes.length); byte[] key = digest.digest(); log("SHA-256 key ", key); SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); return secretKeySpec; } /** * Encrypt and encode message using 256-bit AES with key generated from password. * * * @param password used to generated key * @param message the thing you want to encrypt assumed String UTF-8 * @return Base64 encoded CipherText * @throws GeneralSecurityException if problems occur during encryption */ public static String encrypt(final String password, String message) throws GeneralSecurityException { try { final SecretKeySpec key = generateKey(password); log("message", message); byte[] cipherText = encrypt(key, ivBytes, message.getBytes(CHARSET)); //NO_WRAP is important as was getting \n at the end String encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP); log("Base64.NO_WRAP", encoded); return encoded; } catch (UnsupportedEncodingException e) { if (DEBUG_LOG_ENABLED) Log.e(TAG, "UnsupportedEncodingException ", e); throw new GeneralSecurityException(e); } } /** * More flexible AES encrypt that doesn't encode * @param key AES key typically 128, 192 or 256 bit * @param iv Initiation Vector * @param message in bytes (assumed it's already been decoded) * @return Encrypted cipher text (not encoded) * @throws GeneralSecurityException if something goes wrong during encryption */ public static byte[] encrypt(final SecretKeySpec key, final byte[] iv, final byte[] message) throws GeneralSecurityException { final Cipher cipher = Cipher.getInstance(AES_MODE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); byte[] cipherText = cipher.doFinal(message); log("cipherText", cipherText); return cipherText; } /** * Decrypt and decode ciphertext using 256-bit AES with key generated from password * * @param password used to generated key * @param base64EncodedCipherText the encrpyted message encoded with base64 * @return message in Plain text (String UTF-8) * @throws GeneralSecurityException if there's an issue decrypting */ public static String decrypt(final String password, String base64EncodedCipherText) throws GeneralSecurityException { try { final SecretKeySpec key = generateKey(password); log("base64EncodedCipherText", base64EncodedCipherText); byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP); log("decodedCipherText", decodedCipherText); byte[] decryptedBytes = decrypt(key, ivBytes, decodedCipherText); log("decryptedBytes", decryptedBytes); String message = new String(decryptedBytes, CHARSET); log("message", message); return message; } catch (UnsupportedEncodingException e) { if (DEBUG_LOG_ENABLED) Log.e(TAG, "UnsupportedEncodingException ", e); throw new GeneralSecurityException(e); } } /** * More flexible AES decrypt that doesn't encode * * @param key AES key typically 128, 192 or 256 bit * @param iv Initiation Vector * @param decodedCipherText in bytes (assumed it's already been decoded) * @return Decrypted message cipher text (not encoded) * @throws GeneralSecurityException if something goes wrong during encryption */ public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText) throws GeneralSecurityException { final Cipher cipher = Cipher.getInstance(AES_MODE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); byte[] decryptedBytes = cipher.doFinal(decodedCipherText); log("decryptedBytes", decryptedBytes); return decryptedBytes; } private static void log(String what, byte[] bytes) { if (DEBUG_LOG_ENABLED) Log.d(TAG, what + "[" + bytes.length + "] [" + bytesToHex(bytes) + "]"); } private static void log(String what, String value) { if (DEBUG_LOG_ENABLED) Log.d(TAG, what + "[" + value.length() + "] [" + value + "]"); } /** * Converts byte array to hexidecimal useful for logging and fault finding * @param bytes * @return */ private static String bytesToHex(byte[] bytes) { final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] hexChars = new char[bytes.length * 2]; int v; for (int j = 0; j < bytes.length; j++) { v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } private AESCrypt() { } } ================================================ FILE: core/src/main/java/org/litepal/util/cipher/CipherUtil.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.util.cipher; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * Utility to manage encryption and decryption for different algorithms. * * @author Tony Green * @since 1.6 */ public class CipherUtil { private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; public static String aesKey = "LitePalKey"; /** * Encrypt the plain text with AES algorithm. * @param plainText * The plain text. * @return The Encrypt content. */ public static String aesEncrypt(String plainText) { if (TextUtils.isEmpty(plainText)) { return plainText; } try { return AESCrypt.encrypt(aesKey, plainText); } catch (Exception e) { e.printStackTrace(); } return null; } /** * Decrypt the encrypted text with AES algorithm. * @param encryptedText * The encrypted text. * @return The plain content. */ public static String aesDecrypt(String encryptedText) { if (TextUtils.isEmpty(encryptedText)) { return encryptedText; } try { return AESCrypt.decrypt(aesKey, encryptedText); } catch (Exception e) { e.printStackTrace(); } return null; } /** * Encrypt the plain text with MD5 algorithm. * @param plainText * The plain text. * @return The Encrypt content. */ public static String md5Encrypt(String plainText) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(plainText.getBytes(Charset.defaultCharset())); return new String(toHex(digest.digest())); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } private static char[] toHex(byte[] data) { char[] toDigits = DIGITS_UPPER; int l = data.length; char[] out = new char[l << 1]; // two characters form the hex value. for (int i = 0, j = 0; i < l; i++) { out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; out[j++] = toDigits[0x0F & data[i]]; } return out; } } ================================================ FILE: core/src/main/res/values/strings.xml ================================================ LitePal ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jan 29 20:32:21 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # For Cygwin, ensure paths are in UNIX format before anything is touched. if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` fi # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >&- APP_HOME="`pwd -P`" cd "$SAVED" >&- CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: java/.gitignore ================================================ /build ================================================ FILE: java/bintray.gradle ================================================ group = PROJ_GROUP version = PROJ_VERSION project.archivesBaseName = PROJ_ARTIFACTID_JAVA apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } artifacts { archives sourcesJar } def pomConfig = { licenses { license { name "The Apache Software License, Version 2.0" url "http://www.apache.org/licenses/LICENSE-2.0.txt" distribution "repo" } } developers { developer { id DEVELOPER_ID name DEVELOPER_NAME email DEVELOPER_EMAIL } } } publishing { publications { mavenJava(MavenPublication) { artifactId PROJ_ARTIFACTID_JAVA pom{ packaging 'aar' } // pom.withXml { // def root = asNode() // root.appendNode('description', PROJ_DESCRIPTION) // root.children().last() + pomConfig // } //The publication doesn't know about our dependencies, so we have to manually add them to the pom pom.withXml { def dependenciesNode = asNode().appendNode('dependencies') //Iterate over the compile dependencies (we don't want the test ones), adding a node for each configurations.api.allDependencies.each { def dependencyNode = dependenciesNode.appendNode('dependency') dependencyNode.appendNode('groupId', it.group) dependencyNode.appendNode('artifactId', it.name) dependencyNode.appendNode('version', it.version) } } } } } bintray { user = BINTRAY_USER key = BINTRAY_KEY configurations = ['archives'] publications = ['mavenJava'] publish = true pkg { repo = 'maven' name = PROJ_NAME_JAVA desc = PROJ_DESCRIPTION websiteUrl = PROJ_WEBSITEURL vcsUrl = PROJ_VCSURL licenses = ['Apache-2.0'] publicDownloadNumbers = true } } ================================================ FILE: java/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion 28 defaultConfig { minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { api project(':core') } if (hasProperty("BINTRAY_KEY")) { apply from: 'bintray.gradle' } ================================================ FILE: java/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/tony/Android/Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: java/src/main/AndroidManifest.xml ================================================ ================================================ FILE: java/src/main/java/org/litepal/LitePal.java ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import org.litepal.crud.LitePalSupport; import org.litepal.crud.async.AverageExecutor; import org.litepal.crud.async.CountExecutor; import org.litepal.crud.async.FindExecutor; import org.litepal.crud.async.FindMultiExecutor; import org.litepal.crud.async.SaveExecutor; import org.litepal.crud.async.UpdateOrDeleteExecutor; import org.litepal.tablemanager.callback.DatabaseListener; import java.util.Collection; import java.util.List; /** * LitePal is an Android library that allows developers to use SQLite database extremely easy. * You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to * work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} * methods. * * @author Tony Green * @since 1.4 */ public class LitePal { /** * Initialize to make LitePal ready to work. If you didn't configure LitePalApplication * in the AndroidManifest.xml, make sure you call this method as soon as possible. In * Application's onCreate() method will be fine. * * @param context * Application context. */ public static void initialize(Context context) { Operator.initialize(context); } /** * Get a writable SQLiteDatabase. * * @return A writable SQLiteDatabase instance */ public static SQLiteDatabase getDatabase() { return Operator.getDatabase(); } /** * Switch the using database to the one specified by parameter. * @param litePalDB * The database to switch to. */ public static void use(LitePalDB litePalDB) { Operator.use(litePalDB); } /** * Switch the using database to default with configuration by litepal.xml. */ public static void useDefault() { Operator.useDefault(); } /** * Delete the specified database. * @param dbName * Name of database to delete. * @return True if delete success, false otherwise. */ public static boolean deleteDatabase(String dbName) { return Operator.deleteDatabase(dbName); } public static void aesKey(String key) { Operator.aesKey(key); } /** * Declaring to query which columns in table. * *

     * LitePal.select("name", "age").find(Person.class);
     * 
* * This will find all rows with name and age columns in Person table. * * @param columns * A String array of which columns to return. Passing null will * return all columns. * * @return A FluentQuery instance. */ public static FluentQuery select(String... columns) { return Operator.select(columns); } /** * Declaring to query which rows in table. * *
     * LitePal.where("name = ? or age > ?", "Tom", "14").find(Person.class);
     * 
* * This will find rows which name is Tom or age greater than 14 in Person * table. * * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return A FluentQuery instance. */ public static FluentQuery where(String... conditions) { return Operator.where(conditions); } /** * Declaring how to order the rows queried from table. * *
     * LitePal.order("name desc").find(Person.class);
     * 
* * This will find all rows in Person table sorted by name with inverted * order. * * @param column * How to order the rows, formatted as an SQL ORDER BY clause. * Passing null will use the default sort order, which may be * unordered. * @return A FluentQuery instance. */ public static FluentQuery order(String column) { return Operator.order(column); } /** * Limits the number of rows returned by the query. * *
     * LitePal.limit(2).find(Person.class);
     * 
* * This will find the top 2 rows in Person table. * * @param value * Limits the number of rows returned by the query, formatted as * LIMIT clause. * @return A FluentQuery instance. */ public static FluentQuery limit(int value) { return Operator.limit(value); } /** * Declaring the offset of rows returned by the query. This method must be * used with {@link #limit(int)}, or nothing will return. * *
     * LitePal.limit(1).offset(2).find(Person.class);
     * 
* * This will find the third row in Person table. * * @param value * The offset amount of rows returned by the query. * @return A FluentQuery instance. */ public static FluentQuery offset(int value) { return Operator.offset(value); } /** * Count the records. * *
     * LitePal.count(Person.class);
     * 
* * This will count all rows in person table.
* You can also specify a where clause when counting. * *
     * LitePal.where("age > ?", "15").count(Person.class);
     * 
* * @param modelClass * Which table to query from by class. * @return Count of the specified table. */ public static int count(Class modelClass) { return Operator.count(modelClass); } /** * Basically same as {@link #count(Class)} but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @return A CountExecutor instance. */ public static CountExecutor countAsync(final Class modelClass) { return Operator.countAsync(modelClass); } /** * Count the records. * *
     * LitePal.count("person");
     * 
* * This will count all rows in person table.
* You can also specify a where clause when counting. * *
     * LitePal.where("age > ?", "15").count("person");
     * 
* * @param tableName * Which table to query from. * @return Count of the specified table. */ public static int count(String tableName) { return Operator.count(tableName); } /** * Basically same as {@link #count(String)} but pending to a new thread for executing. * * @param tableName * Which table to query from. * @return A CountExecutor instance. */ public static CountExecutor countAsync(final String tableName) { return Operator.countAsync(tableName); } /** * Calculates the average value on a given column. * *
     * LitePal.average(Person.class, "age");
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").average(Person.class, "age");
     * 
* * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return The average value on a given column. */ public static double average(Class modelClass, String column) { return Operator.average(modelClass, column); } /** * Basically same as {@link #average(Class, String)} but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ public static AverageExecutor averageAsync(final Class modelClass, final String column) { return Operator.averageAsync(modelClass, column); } /** * Calculates the average value on a given column. * *
     * LitePal.average("person", "age");
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").average("person", "age");
     * 
* * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return The average value on a given column. */ public static double average(String tableName, String column) { return Operator.average(tableName, column); } /** * Basically same as {@link #average(String, String)} but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ public static AverageExecutor averageAsync(final String tableName, final String column) { return Operator.averageAsync(tableName, column); } /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.max(Person.class, "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
     * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ public static T max(Class modelClass, String columnName, Class columnType) { return Operator.max(modelClass, columnName, columnType); } /** * Basically same as {@link #max(Class, String, Class)} but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ public static FindExecutor maxAsync(final Class modelClass, final String columnName, final Class columnType) { return Operator.maxAsync(modelClass, columnName, columnType); } /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.max("person", "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").max("person", "age", Integer.TYPE);
     * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ public static T max(String tableName, String columnName, Class columnType) { return Operator.max(tableName, columnName, columnType); } /** * Basically same as {@link #max(String, String, Class)} but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ public static FindExecutor maxAsync(final String tableName, final String columnName, final Class columnType) { return Operator.maxAsync(tableName, columnName, columnType); } /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.min(Person.class, "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
     * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ public static T min(Class modelClass, String columnName, Class columnType) { return Operator.min(modelClass, columnName, columnType); } /** * Basically same as {@link #min(Class, String, Class)} but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ public static FindExecutor minAsync(final Class modelClass, final String columnName, final Class columnType) { return Operator.minAsync(modelClass, columnName, columnType); } /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.min("person", "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").min("person", "age", Integer.TYPE);
     * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ public static T min(String tableName, String columnName, Class columnType) { return Operator.min(tableName, columnName, columnType); } /** * Basically same as {@link #min(String, String, Class)} but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ public static FindExecutor minAsync(final String tableName, final String columnName, final Class columnType) { return Operator.minAsync(tableName, columnName, columnType); } /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.sum(Person.class, "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
     * 
* * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ public static T sum(Class modelClass, String columnName, Class columnType) { return Operator.sum(modelClass, columnName, columnType); } /** * Basically same as {@link #sum(Class, String, Class)} but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ public static FindExecutor sumAsync(final Class modelClass, final String columnName, final Class columnType) { return Operator.sumAsync(modelClass, columnName, columnType); } /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * *
     * LitePal.sum("person", "age", int.class);
     * 
* * You can also specify a where clause when calculating. * *
     * LitePal.where("age > ?", "15").sum("person", "age", Integer.TYPE);
     * 
* * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ public static T sum(String tableName, String columnName, Class columnType) { return Operator.sum(tableName, columnName, columnType); } /** * Basically same as {@link #sum(String, String, Class)} but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ public static FindExecutor sumAsync(final String tableName, final String columnName, final Class columnType) { return Operator.sumAsync(tableName, columnName, columnType); } /** * Finds the record by a specific id. * *
     * Person p = LitePal.find(Person.class, 1);
     * 
* * The modelClass determines which table to query and the object type to * return. If no record can be found, then return null.
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link LitePal#find(Class, long, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return An object with found data from database, or null. */ public static T find(Class modelClass, long id) { return Operator.find(modelClass, id); } /** * Basically same as {@link #find(Class, long)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return A FindExecutor instance. */ public static FindExecutor findAsync(Class modelClass, long id) { return Operator.findAsync(modelClass, id); } /** * It is mostly same as {@link LitePal#find(Class, long)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ public static T find(Class modelClass, long id, boolean isEager) { return Operator.find(modelClass, id, isEager); } /** * Basically same as {@link #find(Class, long, boolean)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ public static FindExecutor findAsync(final Class modelClass, final long id, final boolean isEager) { return Operator.findAsync(modelClass, id, isEager); } /** * Finds the first record of a single table. * *
     * Person p = LitePal.findFirst(Person.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link LitePal#findFirst(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of first row, or null. */ public static T findFirst(Class modelClass) { return Operator.findFirst(modelClass); } /** * Basically same as {@link #findFirst(Class)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @return A FindExecutor instance. */ public static FindExecutor findFirstAsync(Class modelClass) { return Operator.findFirstAsync(modelClass); } /** * It is mostly same as {@link LitePal#findFirst(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ public static T findFirst(Class modelClass, boolean isEager) { return Operator.findFirst(modelClass, isEager); } /** * Basically same as {@link #findFirst(Class, boolean)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ public static FindExecutor findFirstAsync(final Class modelClass, final boolean isEager) { return Operator.findFirstAsync(modelClass, isEager); } /** * Finds the last record of a single table. * *
     * Person p = LitePal.findLast(Person.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link LitePal#findLast(Class, boolean)}. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of last row, or null. */ public static T findLast(Class modelClass) { return Operator.findLast(modelClass); } /** * Basically same as {@link #findLast(Class)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @return A FindExecutor instance. */ public static FindExecutor findLastAsync(Class modelClass) { return Operator.findLastAsync(modelClass); } /** * It is mostly same as {@link LitePal#findLast(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ public static T findLast(Class modelClass, boolean isEager) { return Operator.findLast(modelClass, isEager); } /** * Basically same as {@link #findLast(Class, boolean)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ public static FindExecutor findLastAsync(final Class modelClass, final boolean isEager) { return Operator.findLastAsync(modelClass, isEager); } /** * Finds multiple records by an id array. * *
     * List<Person> people = LitePal.findAll(Person.class, 1, 2, 3);
     *
     * long[] bookIds = { 10, 18 };
     * List<Book> books = LitePal.findAll(Book.class, bookIds);
     * 
* * Of course you can find all records by passing nothing to the ids * parameter. * *
     * List<Book> allBooks = LitePal.findAll(Book.class);
     * 
* * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link LitePal#findAll(Class, boolean, long...)}. * * The modelClass determines which table to query and the object type to * return. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ public static List findAll(Class modelClass, long... ids) { return Operator.findAll(modelClass, ids); } /** * Basically same as {@link #findAll(Class, long...)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ public static FindMultiExecutor findAllAsync(Class modelClass, long... ids) { return Operator.findAllAsync(modelClass, ids); } /** * It is mostly same as {@link LitePal#findAll(Class, long...)} but an * isEager parameter. If set true the associated models will be loaded as well. *
* Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ public static List findAll(Class modelClass, boolean isEager, long... ids) { return Operator.findAll(modelClass, isEager, ids); } /** * Basically same as {@link #findAll(Class, boolean, long...)} but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ public static FindMultiExecutor findAllAsync(final Class modelClass, final boolean isEager, final long... ids) { return Operator.findAllAsync(modelClass, isEager, ids); } /** * Runs the provided SQL and returns a Cursor over the result set. You may * include ? in where clause in the query, which will be replaced by the * second to the last parameters, such as: * *
     * Cursor cursor = LitePal.findBySQL("select * from person where name=? and age=?", "Tom", "14");
     * 
* * @param sql * First parameter is the SQL clause to apply. Second to the last * parameters will replace the place holders. * @return A Cursor object, which is positioned before the first entry. Note * that Cursors are not synchronized, see the documentation for more * details. */ public static Cursor findBySQL(String... sql) { return Operator.findBySQL(sql); } /** * Deletes the record in the database by id.
* The data in other tables which is referenced with the record will be * removed too. * *
     * LitePal.delete(Person.class, 1);
     * 
* * This means that the record 1 in person table will be removed. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ public static int delete(Class modelClass, long id) { return Operator.delete(modelClass, id); } /** * Basically same as {@link #delete(Class, long)} but pending to a new thread for executing. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return A UpdateOrDeleteExecutor instance. */ public static UpdateOrDeleteExecutor deleteAsync(final Class modelClass, final long id) { return Operator.deleteAsync(modelClass, id); } /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * *
     * LitePal.deleteAll(Person.class, "name = ? and age = ?", "Tom", "14");
     * 
* * This means that all the records which name is Tom and age is 14 will be * removed.
* * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int deleteAll(Class modelClass, String... conditions) { return Operator.deleteAll(modelClass, conditions); } /** * Basically same as {@link #deleteAll(Class, String...)} but pending to a new thread for executing. * * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ public static UpdateOrDeleteExecutor deleteAllAsync(final Class modelClass, final String... conditions) { return Operator.deleteAllAsync(modelClass, conditions); } /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * *
     * LitePal.deleteAll("person", "name = ? and age = ?", "Tom", "14");
     * 
* * This means that all the records which name is Tom and age is 14 will be * removed.
* * Note that this method won't delete the referenced data in other tables. * You should remove those values by your own. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int deleteAll(String tableName, String... conditions) { return Operator.deleteAll(tableName, conditions); } /** * Basically same as {@link #deleteAll(String, String...)} but pending to a new thread for executing. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ public static UpdateOrDeleteExecutor deleteAllAsync(final String tableName, final String... conditions) { return Operator.deleteAllAsync(tableName, conditions); } /** * Updates the corresponding record by id with ContentValues. Returns the * number of affected rows. * *
     * ContentValues cv = new ContentValues();
     * cv.put("name", "Jim");
     * LitePal.update(Person.class, cv, 1);
     * 
* * This means that the name of record 1 will be updated into Jim.
* * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return The number of rows affected. */ public static int update(Class modelClass, ContentValues values, long id) { return Operator.update(modelClass, values, id); } /** * Basically same as {@link #update(Class, ContentValues, long)} but pending to a new thread for executing. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return A UpdateOrDeleteExecutor instance. */ public static UpdateOrDeleteExecutor updateAsync(final Class modelClass, final ContentValues values, final long id) { return Operator.updateAsync(modelClass, values, id); } /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * *
     * ContentValues cv = new ContentValues();
     * cv.put("name", "Jim");
     * LitePal.update(Person.class, cv, "name = ?", "Tom");
     * 
* * This means that all the records which name is Tom will be updated into * Jim. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int updateAll(Class modelClass, ContentValues values, String... conditions) { return Operator.updateAll(modelClass, values, conditions); } /** * Basically same as {@link #updateAll(Class, ContentValues, String...)} but pending to a new thread for executing. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ public static UpdateOrDeleteExecutor updateAllAsync(Class modelClass, ContentValues values, String... conditions) { return Operator.updateAllAsync(modelClass, values, conditions); } /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * *
     * ContentValues cv = new ContentValues();
     * cv.put("name", "Jim");
     * LitePal.update("person", cv, "name = ?", "Tom");
     * 
* * This means that all the records which name is Tom will be updated into * Jim. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ public static int updateAll(String tableName, ContentValues values, String... conditions) { return Operator.updateAll(tableName, values, conditions); } /** * Basically same as {@link #updateAll(String, ContentValues, String...)} but pending to a new thread for executing. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ public static UpdateOrDeleteExecutor updateAllAsync(final String tableName, final ContentValues values, final String... conditions) { return Operator.updateAllAsync(tableName, values, conditions); } /** * Saves the collection into database.
* *
     * LitePal.saveAll(people);
     * 
* * If the model in collection is a new record gets created in the database, * otherwise the existing record gets updated.
* If saving process failed by any accident, the whole action will be * cancelled and your database will be rolled back.
* This method acts the same result as the below way, but much more * efficient. * *
     * for (Person person : people) {
     * 	person.save();
     * }
     * 
* * So when your collection holds huge of models, saveAll(Collection) is the better choice. * * @param collection * Holds all models to save. */ public static void saveAll(Collection collection) { Operator.saveAll(collection); } /** * Basically same as {@link #saveAll(Collection)} but pending to a new thread for executing. * * @param collection * Holds all models to save. * @return A SaveExecutor instance. */ public static SaveExecutor saveAllAsync(final Collection collection) { return Operator.saveAllAsync(collection); } /** * Provide a way to mark all models in collection as deleted. This means these models' save * state is no longer exist anymore. If save them again, they will be treated as inserting new * data instead of updating the exist one. * @param collection * Collection of models which want to mark as deleted and clear their save state. */ public static void markAsDeleted(Collection collection) { Operator.markAsDeleted(collection); } /** * Check if the specified conditions data already exists in the table. * @param modelClass * Which table to check by class. * @param conditions * A filter declaring which data to check. Exactly same use as * {@link LitePal#where(String...)}, except null conditions will result in false. * @return Return true if the specified conditions data already exists in the table. * False otherwise. Null conditions will result in false. */ public static boolean isExist(Class modelClass, String... conditions) { return Operator.isExist(modelClass, conditions); } /** * Register a listener to listen database create and upgrade events. */ public static void registerDatabaseListener(DatabaseListener listener) { Operator.registerDatabaseListener(listener); } } ================================================ FILE: java/src/main/res/values/strings.xml ================================================ LitePal ================================================ FILE: kotlin/.gitignore ================================================ /build ================================================ FILE: kotlin/bintray.gradle ================================================ group = PROJ_GROUP version = PROJ_VERSION project.archivesBaseName = PROJ_ARTIFACTID_KT apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish' task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' } artifacts { archives sourcesJar } def pomConfig = { licenses { license { name "The Apache Software License, Version 2.0" url "http://www.apache.org/licenses/LICENSE-2.0.txt" distribution "repo" } } developers { developer { id DEVELOPER_ID name DEVELOPER_NAME email DEVELOPER_EMAIL } } } publishing { publications { mavenJava(MavenPublication) { artifactId PROJ_ARTIFACTID_KT pom{ packaging 'aar' } // pom.withXml { // def root = asNode() // root.appendNode('description', PROJ_DESCRIPTION) // root.children().last() + pomConfig // } //The publication doesn't know about our dependencies, so we have to manually add them to the pom pom.withXml { def dependenciesNode = asNode().appendNode('dependencies') //Iterate over the compile dependencies (we don't want the test ones), adding a node for each configurations.api.allDependencies.each { def dependencyNode = dependenciesNode.appendNode('dependency') dependencyNode.appendNode('groupId', it.group) dependencyNode.appendNode('artifactId', it.name) dependencyNode.appendNode('version', it.version) } } } } } bintray { user = BINTRAY_USER key = BINTRAY_KEY configurations = ['archives'] publications = ['mavenJava'] publish = true pkg { repo = 'maven' name = PROJ_NAME_KT desc = PROJ_DESCRIPTION websiteUrl = PROJ_WEBSITEURL vcsUrl = PROJ_VCSURL licenses = ['Apache-2.0'] publicDownloadNumbers = true } } ================================================ FILE: kotlin/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'org.jetbrains.dokka-android' android { compileSdkVersion 28 defaultConfig { minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" api project(':core') } if (hasProperty("BINTRAY_KEY")) { apply from: 'bintray.gradle' } ================================================ FILE: kotlin/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/tony/Android/Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: kotlin/src/main/AndroidManifest.xml ================================================ ================================================ FILE: kotlin/src/main/java/org/litepal/LitePal.kt ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal import android.content.ContentValues import android.content.Context import android.database.Cursor import android.database.sqlite.SQLiteDatabase import android.os.Handler import android.os.Looper import android.text.TextUtils import org.litepal.crud.* import org.litepal.crud.async.* import org.litepal.exceptions.LitePalSupportException import org.litepal.parser.LitePalAttr import org.litepal.parser.LitePalParser import org.litepal.tablemanager.Connector import org.litepal.tablemanager.callback.DatabaseListener import org.litepal.util.BaseUtility import org.litepal.util.Const import org.litepal.util.DBUtility import org.litepal.util.SharedUtil import org.litepal.util.cipher.CipherUtil import java.io.File import kotlin.math.tan /** * LitePal is an Android library that allows developers to use SQLite database extremely easy. * You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to * work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} * methods. * * @author Tony Green * @since 2.0 */ object LitePal { /** * Initialize to make LitePal ready to work. If you didn't configure LitePalApplication * in the AndroidManifest.xml, make sure you call this method as soon as possible. In * Application's onCreate() method will be fine. * * @param context * Application context. */ @JvmStatic fun initialize(context: Context) { Operator.initialize(context) } /** * Get a writable SQLiteDatabase. * * @return A writable SQLiteDatabase instance */ @JvmStatic fun getDatabase(): SQLiteDatabase = Operator.getDatabase() /** * Switch the using database to the one specified by parameter. * @param litePalDB * The database to switch to. */ @JvmStatic fun use(litePalDB: LitePalDB) { Operator.use(litePalDB) } /** * Switch the using database to default with configuration by litepal.xml. */ @JvmStatic fun useDefault() { Operator.useDefault() } /** * Delete the specified database. * @param dbName * Name of database to delete. * @return True if delete success, false otherwise. */ @JvmStatic fun deleteDatabase(dbName: String) = Operator.deleteDatabase(dbName) @JvmStatic fun aesKey(key: String) { Operator.aesKey(key) } /** * Declaring to query which columns in table. * * LitePal.select("name", "age").find(Person.class); * * This will find all rows with name and age columns in Person table. * * @param columns * A String array of which columns to return. Passing null will * return all columns. * * @return A FluentQuery instance. */ @JvmStatic fun select(vararg columns: String?) = Operator.select(*columns) /** * Declaring to query which rows in table. * * LitePal.where("name = ? or age > ?", "Tom", "14").find(Person.class); * * This will find rows which name is Tom or age greater than 14 in Person * table. * * @param conditions * A filter declaring which rows to return, formatted as an SQL * WHERE clause. Passing null will return all rows. * @return A FluentQuery instance. */ @JvmStatic fun where(vararg conditions: String?) = Operator.where(*conditions) /** * Declaring how to order the rows queried from table. * * LitePal.order("name desc").find(Person.class); * * This will find all rows in Person table sorted by name with inverted * order. * * @param column * How to order the rows, formatted as an SQL ORDER BY clause. * Passing null will use the default sort order, which may be * unordered. * @return A FluentQuery instance. */ @JvmStatic fun order(column: String?) = Operator.order(column) /** * Limits the number of rows returned by the query. * * LitePal.limit(2).find(Person.class); * * This will find the top 2 rows in Person table. * * @param value * Limits the number of rows returned by the query, formatted as * LIMIT clause. * @return A FluentQuery instance. */ @JvmStatic fun limit(value: Int) = Operator.limit(value) /** * Declaring the offset of rows returned by the query. This method must be * used with [LitePal.limit], or nothing will return. * * LitePal.limit(1).offset(2).find(Person.class); * * This will find the third row in Person table. * * @param value * The offset amount of rows returned by the query. * @return A FluentQuery instance. */ @JvmStatic fun offset(value: Int) = Operator.offset(value) /** * Count the records. * * LitePal.count(Person.class); * * This will count all rows in person table. * * You can also specify a where clause when counting. * * LitePal.where("age > ?", "15").count(Person.class); * * @param modelClass * Which table to query from by class. * @return Count of the specified table. */ @JvmStatic fun count(modelClass: Class<*>) = Operator.count(modelClass) /** * Basically same as [LitePal.count] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @return A CountExecutor instance. */ @JvmStatic fun countAsync(modelClass: Class<*>) = Operator.countAsync(modelClass) /** * Count the records. * * LitePal.count("person"); * * This will count all rows in person table. * * You can also specify a where clause when counting. * * LitePal.where("age > ?", "15").count("person"); * * @param tableName * Which table to query from. * @return Count of the specified table. */ @JvmStatic fun count(tableName: String) = Operator.count(tableName) /** * Basically same as [LitePal.count] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @return A CountExecutor instance. */ @JvmStatic fun countAsync(tableName: String) = Operator.countAsync(tableName) /** * Calculates the average value on a given column. * * LitePal.average(Person.class, "age"); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").average(Person.class, "age"); * * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return The average value on a given column. */ @JvmStatic fun average(modelClass: Class<*>, column: String) = Operator.average(modelClass, column) /** * Basically same as [LitePal.average] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ @JvmStatic fun averageAsync(modelClass: Class<*>, column: String) = Operator.averageAsync(modelClass, column) /** * Calculates the average value on a given column. * * LitePal.average("person", "age"); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").average("person", "age"); * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return The average value on a given column. */ @JvmStatic fun average(tableName: String, column: String) = Operator.average(tableName, column) /** * Basically same as [LitePal.average] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ @JvmStatic fun averageAsync(tableName: String, column: String) = Operator.averageAsync(tableName, column) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max(Person.class, "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max(Person.class, "age", Integer.TYPE); * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ @JvmStatic fun max(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.max(modelClass, columnName, columnType) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic fun maxAsync(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.maxAsync(modelClass, columnName, columnType) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max("person", "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max("person", "age", Integer.TYPE); * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The maximum value on a given column. */ @JvmStatic fun max(tableName: String, columnName: String, columnType: Class) = Operator.max(tableName, columnName, columnType) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic fun maxAsync(tableName: String, columnName: String, columnType: Class) = Operator.maxAsync(tableName, columnName, columnType) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min(Person.class, "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min(Person.class, "age", Integer.TYPE); * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ @JvmStatic fun min(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.min(modelClass, columnName, columnType) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic fun minAsync(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.minAsync(modelClass, columnName, columnType) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min("person", "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min("person", "age", Integer.TYPE); * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The minimum value on a given column. */ @JvmStatic fun min(tableName: String, columnName: String, columnType: Class) = Operator.min(tableName, columnName, columnType) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic fun minAsync(tableName: String, columnName: String, columnType: Class) = Operator.minAsync(tableName, columnName, columnType) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum(Person.class, "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE); * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ @JvmStatic fun sum(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.sum(modelClass, columnName, columnType) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param modelClass * Which table to query from by class. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic fun sumAsync(modelClass: Class<*>, columnName: String, columnType: Class) = Operator.sumAsync(modelClass, columnName, columnType) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum("person", "age", int.class); * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum("person", "age", Integer.TYPE); * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return The sum value on a given column. */ @JvmStatic fun sum(tableName: String, columnName: String, columnType: Class) = Operator.sum(tableName, columnName, columnType) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @param columnType * The type of the based on column. * @return A FindExecutor instance. */ @JvmStatic fun sumAsync(tableName: String, columnName: String, columnType: Class) = Operator.sumAsync(tableName, columnName, columnType) /** * Finds the record by a specific id. * * Person p = LitePal.find(Person.class, 1); * * The modelClass determines which table to query and the object type to * return. If no record can be found, then return null. * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.find]. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return An object with found data from database, or null. */ @JvmStatic fun find(modelClass: Class, id: Long) = Operator.find(modelClass, id) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @return A FindExecutor instance. */ @JvmStatic fun findAsync(modelClass: Class, id: Long) = Operator.findAsync(modelClass, id) /** * It is mostly same as [LitePal.find] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ @JvmStatic fun find(modelClass: Class, id: Long, isEager: Boolean) = Operator.find(modelClass, id, isEager) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @JvmStatic fun findAsync(modelClass: Class, id: Long, isEager: Boolean) = Operator.findAsync(modelClass, id, isEager) /** * Finds the first record of a single table. * * Person p = LitePal.findFirst(Person.class); * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findFirst]. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of first row, or null. */ @JvmStatic fun findFirst(modelClass: Class) = Operator.findFirst(modelClass) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @return A FindExecutor instance. */ @JvmStatic fun findFirstAsync(modelClass: Class) = Operator.findFirstAsync(modelClass) /** * It is mostly same as [LitePal.findFirst] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ @JvmStatic fun findFirst(modelClass: Class, isEager: Boolean) = Operator.findFirst(modelClass, isEager) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @JvmStatic fun findFirstAsync(modelClass: Class, isEager: Boolean) = Operator.findFirstAsync(modelClass, isEager) /** * Finds the last record of a single table. * * Person p = LitePal.findLast(Person.class); * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findLast]. * * @param modelClass * Which table to query and the object type to return. * @return An object with data of last row, or null. */ @JvmStatic fun findLast(modelClass: Class) = Operator.findLast(modelClass) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @return A FindExecutor instance. */ @JvmStatic fun findLastAsync(modelClass: Class) = Operator.findLastAsync(modelClass) /** * It is mostly same as [LitePal.findLast] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ @JvmStatic fun findLast(modelClass: Class, isEager: Boolean) = Operator.findLast(modelClass, isEager) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ @JvmStatic fun findLastAsync(modelClass: Class, isEager: Boolean) = Operator.findLastAsync(modelClass, isEager) /** * Finds multiple records by an id array. * * List<Person> people = LitePal.findAll(Person.class, 1, 2, 3); * * long[] bookIds = { 10, 18 }; * List<Book> books = LitePal.findAll(Book.class, bookIds); * * Of course you can find all records by passing nothing to the ids * parameter. * * List<Book> allBooks = LitePal.findAll(Book.class); * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findAll]. * * The modelClass determines which table to query and the object type to * return. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ @JvmStatic fun findAll(modelClass: Class, vararg ids: Long) = Operator.findAll(modelClass, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return as a list. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ @JvmStatic fun findAllAsync(modelClass: Class, vararg ids: Long) = Operator.findAllAsync(modelClass, *ids) /** * It is mostly same as [LitePal.findAll] but an * isEager parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ @JvmStatic fun findAll(modelClass: Class, isEager: Boolean, vararg ids: Long) = Operator.findAll(modelClass, isEager, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param modelClass * Which table to query and the object type to return as a list. * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ @JvmStatic fun findAllAsync(modelClass: Class, isEager: Boolean, vararg ids: Long) = Operator.findAllAsync(modelClass, isEager, *ids) /** * Runs the provided SQL and returns a Cursor over the result set. You may * include ? in where clause in the query, which will be replaced by the * second to the last parameters, such as: * * Cursor cursor = LitePal.findBySQL("select * from person where name=? and age=?", "Tom", "14"); * * @param sql * First parameter is the SQL clause to apply. Second to the last * parameters will replace the place holders. * @return A Cursor object, which is positioned before the first entry. Note * that Cursors are not synchronized, see the documentation for more * details. */ @JvmStatic fun findBySQL(vararg sql: String) = Operator.findBySQL(*sql) /** * Deletes the record in the database by id. * * The data in other tables which is referenced with the record will be * removed too. * * LitePal.delete(Person.class, 1); * * This means that the record 1 in person table will be removed. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ @JvmStatic fun delete(modelClass: Class<*>, id: Long) = Operator.delete(modelClass, id) /** * Basically same as [LitePal.delete] but pending to a new thread for executing. * * @param modelClass * Which table to delete from by class. * @param id * Which record to delete. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic fun deleteAsync(modelClass: Class<*>, id: Long) = Operator.deleteAsync(modelClass, id) /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * * LitePal.deleteAll(Person.class, "name = ? and age = ?", "Tom", "14"); * * This means that all the records which name is Tom and age is 14 will be * removed. * * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return The number of rows affected. */ @JvmStatic fun deleteAll(modelClass: Class<*>, vararg conditions: String?) = Operator.deleteAll(modelClass, *conditions) /** * Basically same as [LitePal.deleteAll] but pending to a new thread for executing. * * @param modelClass * Which table to delete from by class. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic fun deleteAllAsync(modelClass: Class<*>, vararg conditions: String?) = Operator.deleteAllAsync(modelClass, *conditions) /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * * LitePal.deleteAll("person", "name = ? and age = ?", "Tom", "14"); * * This means that all the records which name is Tom and age is 14 will be * removed. * * Note that this method won't delete the referenced data in other tables. * You should remove those values by your own. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return The number of rows affected. */ @JvmStatic fun deleteAll(tableName: String, vararg conditions: String?) = Operator.deleteAll(tableName, *conditions) /** * Basically same as [LitePal.deleteAll] but pending to a new thread for executing. * * @param tableName * Which table to delete from. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic fun deleteAllAsync(tableName: String, vararg conditions: String?) = Operator.deleteAllAsync(tableName, *conditions) /** * Updates the corresponding record by id with ContentValues. Returns the * number of affected rows. * * ContentValues cv = new ContentValues(); * * cv.put("name", "Jim"); * * LitePal.update(Person.class, cv, 1); * * This means that the name of record 1 will be updated into Jim. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return The number of rows affected. */ @JvmStatic fun update(modelClass: Class<*>, values: ContentValues, id: Long) = Operator.update(modelClass, values, id) /** * Basically same as [LitePal.update] but pending to a new thread for executing. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic fun updateAsync(modelClass: Class<*>, values: ContentValues, id: Long) = Operator.updateAsync(modelClass, values, id) /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * * ContentValues cv = new ContentValues(); * * cv.put("name", "Jim"); * * LitePal.update(Person.class, cv, "name = ?", "Tom"); * * This means that all the records which name is Tom will be updated into * Jim. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ @JvmStatic fun updateAll(modelClass: Class<*>, values: ContentValues, vararg conditions: String?) = Operator.updateAll(modelClass, values, *conditions) /** * Basically same as [LitePal.updateAll] but pending to a new thread for executing. * * @param modelClass * Which table to update by class. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic fun updateAllAsync(modelClass: Class<*>, values: ContentValues, vararg conditions: String?) = Operator.updateAllAsync(modelClass, values, *conditions) /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * * ContentValues cv = new ContentValues(); * * cv.put("name", "Jim"); * * LitePal.update("person", cv, "name = ?", "Tom"); * * This means that all the records which name is Tom will be updated into * Jim. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ @JvmStatic fun updateAll(tableName: String, values: ContentValues, vararg conditions: String?) = Operator.updateAll(tableName, values, *conditions) /** * Basically same as [LitePal.updateAll] but pending to a new thread for executing. * * @param tableName * Which table to update. * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ @JvmStatic fun updateAllAsync(tableName: String, values: ContentValues, vararg conditions: String?) = Operator.updateAllAsync(tableName, values, *conditions) /** * Saves the collection into database. * * LitePal.saveAll(people); * * If the model in collection is a new record gets created in the database, * otherwise the existing record gets updated. * * If saving process failed by any accident, the whole action will be * cancelled and your database will be **rolled back**. * * This method acts the same result as the below way, but **much more * efficient**. * * for (Person person : people) { * person.save(); * } * * So when your collection holds huge of models, saveAll(Collection) is the better choice. * * @param collection * Holds all models to save. */ @JvmStatic fun saveAll(collection: Collection) = Operator.saveAll(collection) /** * Basically same as [LitePal.saveAll] but pending to a new thread for executing. * * @param collection * Holds all models to save. * @return A SaveExecutor instance. */ @JvmStatic fun saveAllAsync(collection: Collection) = Operator.saveAllAsync(collection) /** * Provide a way to mark all models in collection as deleted. This means these models' save * state is no longer exist anymore. If save them again, they will be treated as inserting new * data instead of updating the exist one. * @param collection * Collection of models which want to mark as deleted and clear their save state. */ @JvmStatic fun markAsDeleted(collection: Collection) { Operator.markAsDeleted(collection) } /** * Check if the specified conditions data already exists in the table. * @param modelClass * Which table to check by class. * @param conditions * A filter declaring which data to check. Exactly same use as * [LitePal.where], except null conditions will result in false. * @return Return true if the specified conditions data already exists in the table. * False otherwise. Null conditions will result in false. */ @JvmStatic fun isExist(modelClass: Class, vararg conditions: String?) = Operator.isExist(modelClass, *conditions) /** * Register a listener to listen database create and upgrade events. */ @JvmStatic fun registerDatabaseListener(listener: DatabaseListener) { Operator.registerDatabaseListener(listener) } } ================================================ FILE: kotlin/src/main/java/org/litepal/extension/FluentQuery.kt ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.extension import org.litepal.FluentQuery import org.litepal.crud.async.FindExecutor /** * Extension of FluentQuery class for Kotlin api. * @author Tony Green * @since 2.1 */ /** * Finds multiple records by the cluster parameters. You can use the below * way to finish a complicated query: * * LitePal.select("name").where("age > ?", "14").order("age").limit(1).offset(2).find<Person>() * * You can also do the same job with SQLiteDatabase like this: * * getSQLiteDatabase().query("Person", "name", "age > ?", new String[] { "14" }, null, null, "age", * "2,1") * * Obviously, the first way is much more semantic.
* Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#find(Class, boolean)}. * * @return An object list with founded data from database, or an empty list. */ inline fun FluentQuery.find(): List = find(T::class.java) /** * Basically same as {@link #find(Class)} but pending to a new thread for executing. * * @return A FindMultiExecutor instance. */ inline fun FluentQuery.findAsync() = findAsync(T::class.java) /** * It is mostly same as {@link FluentQuery#find(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object list with founded data from database, or an empty list. */ inline fun FluentQuery.find(isEager: Boolean): List = find(T::class.java, isEager) /** * Basically same as {@link #find(Class, boolean)} but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @return A FindMultiExecutor instance. */ inline fun FluentQuery.findAsync(isEager: Boolean) = findAsync(T::class.java, isEager) /** * Finds the first record by the cluster parameters. You can use the below * way to finish a complicated query: * * LitePal.select("name").where("age > ?", "14").order("age").limit(10).offset(2).findFirst<Person>() * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#findFirst(Class, boolean)}. * * @return An object with founded data from database, or null. */ inline fun FluentQuery.findFirst(): T? = findFirst(T::class.java) /** * Basically same as {@link #findFirst(Class)} but pending to a new thread for executing. * * @return A FindExecutor instance. */ inline fun FluentQuery.findFirstAsync(): FindExecutor = findFirstAsync(T::class.java) /** * It is mostly same as {@link FluentQuery#findFirst(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with founded data from database, or null. */ inline fun FluentQuery.findFirst(isEager: Boolean): T? = findFirst(T::class.java, isEager) /** * Finds the last record by the cluster parameters. You can use the below * way to finish a complicated query: * * LitePal.select("name").where("age > ?", "14").order("age").limit(10).offset(2).findLast<Person>() * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * {@link FluentQuery#findLast(Class, boolean)}. * * @return An object with founded data from database, or null. */ inline fun FluentQuery.findLast(): T? = findLast(T::class.java) /** * It is mostly same as {@link FluentQuery#findLast(Class)} but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with founded data from database, or null. */ inline fun FluentQuery.findLast(isEager: Boolean): T? = findLast(T::class.java, isEager) /** * Count the records. * * LitePal.count<Person>() * * This will count all rows in person table. * * You can also specify a where clause when counting. * * LitePal.where("age > ?", "15").count<Person>() * * @return Count of the specified table. */ inline fun FluentQuery.count() = count(T::class.java) /** * Calculates the average value on a given column. * * LitePal.average<Person>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").average<Person>("age") * * @param column * The based on column to calculate. * @return The average value on a given column. */ inline fun FluentQuery.average(column: String) = average(T::class.java, column) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max<Person, Int>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max<Person, Int>("age") * * @param columnName * The based on column to calculate. * * @return The maximum value on a given column. */ inline fun FluentQuery.max(columnName: String): R = max(T::class.java, columnName, R::class.java) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max<Int>("person", "age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max<Int>("person", "age") * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The maximum value on a given column. */ inline fun FluentQuery.max(tableName: String, columnName: String): R = max(tableName, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min<Person, Int>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min<Person, Int>("age") * * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun FluentQuery.min(columnName: String): R = min(T::class.java, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min<Int>("person", "age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min<Int>("person", "age") * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun FluentQuery.min(tableName: String, columnName: String): R = min(tableName, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum<Person, Int>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum<Person, Int>("age") * * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun FluentQuery.sum(columnName: String): R = sum(T::class.java, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum<Int>("person", "age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum<Int>("person", "age") * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun FluentQuery.sum(tableName: String, columnName: String): R = sum(tableName, columnName, R::class.java) ================================================ FILE: kotlin/src/main/java/org/litepal/extension/LitePal.kt ================================================ /* * Copyright (C) Tony Green, LitePal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.extension import android.content.ContentValues import org.litepal.LitePal /** * Extension of LitePal class for Kotlin api. * @author Tony Green * @since 2.1 */ /** * Count the records. * * LitePal.count<Person>() * * This will count all rows in person table. * * You can also specify a where clause when counting. * * LitePal.where("age > ?", "15").count<Person>() * * @return Count of the specified table. */ inline fun LitePal.count() = count(T::class.java) /** * Basically same as [LitePal.count] but pending to a new thread for executing. * * @return A CountExecutor instance. */ inline fun LitePal.countAsync() = countAsync(T::class.java) /** * Calculates the average value on a given column. * * LitePal.average<Person>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").average<Person>("age") * * @param column * The based on column to calculate. * @return The average value on a given column. */ inline fun LitePal.average(column: String) = average(T::class.java, column) /** * Basically same as [LitePal.average] but pending to a new thread for executing. * * @param column * The based on column to calculate. * @return A AverageExecutor instance. */ inline fun LitePal.averageAsync(column: String) = averageAsync(T::class.java, column) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max<Person, Int>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max<Person, Int>("age") * * @param columnName * The based on column to calculate. * * @return The maximum value on a given column. */ inline fun LitePal.max(columnName: String) = max(T::class.java, columnName, R::class.java) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ inline fun LitePal.maxAsync(columnName: String) = maxAsync(T::class.java, columnName, R::class.java) /** * Calculates the maximum value on a given column. The value is returned * with the same data type of the column. * * LitePal.max<Int>("person", "age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").max<Int>("person", "age") * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The maximum value on a given column. */ inline fun LitePal.max(tableName: String, columnName: String) = max(tableName, columnName, R::class.java) /** * Basically same as [LitePal.max] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ inline fun LitePal.maxAsync(tableName: String, columnName: String) = maxAsync(tableName, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min<Person, Int>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min<Person, Int>("age") * * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun LitePal.min(columnName: String) = min(T::class.java, columnName, R::class.java) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ inline fun LitePal.minAsync(columnName: String) = minAsync(T::class.java, columnName, R::class.java) /** * Calculates the minimum value on a given column. The value is returned * with the same data type of the column. * * LitePal.min<Int>("person", "age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").min<Int>("person", "age") * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The minimum value on a given column. */ inline fun LitePal.min(tableName: String, columnName: String) = min(tableName, columnName, R::class.java) /** * Basically same as [LitePal.min] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ inline fun LitePal.minAsync(tableName: String, columnName: String) = minAsync(tableName, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum<Person, Int>("age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum<Person, Int>("age") * * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun LitePal.sum(columnName: String) = sum(T::class.java, columnName, R::class.java) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ inline fun LitePal.sumAsync(columnName: String) = sumAsync(T::class.java, columnName, R::class.java) /** * Calculates the sum of values on a given column. The value is returned * with the same data type of the column. * * LitePal.sum<Int>("person", "age") * * You can also specify a where clause when calculating. * * LitePal.where("age > ?", "15").sum<Int>("person", "age") * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return The sum value on a given column. */ inline fun LitePal.sum(tableName: String, columnName: String) = sum(tableName, columnName, R::class.java) /** * Basically same as [LitePal.sum] but pending to a new thread for executing. * * @param tableName * Which table to query from. * @param columnName * The based on column to calculate. * @return A FindExecutor instance. */ inline fun LitePal.sumAsync(tableName: String, columnName: String) = sumAsync(tableName, columnName, R::class.java) /** * Finds the record by a specific id. * * val person = LitePal.find<Person>(1) * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using[LitePal.find] with isEager parameter. * * @param id * Which record to query. * @return An object with found data from database, or null. */ inline fun LitePal.find(id: Long): T? = find(T::class.java, id) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param id * Which record to query. * @return A FindExecutor instance. */ inline fun LitePal.findAsync(id: Long) = findAsync(T::class.java, id) /** * It is mostly same as [LitePal.find] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return An object with found data from database, or null. */ inline fun LitePal.find(id: Long, isEager: Boolean) = find(T::class.java, id, isEager) /** * Basically same as [LitePal.find] but pending to a new thread for executing. * * @param id * Which record to query. * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ inline fun LitePal.findAsync(id: Long, isEager: Boolean) = find(T::class.java, id, isEager) /** * Finds the first record of a single table. * * val person = LitePal.findFirst<Person>() * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findFirst] with isEager parameter. * * @return An object with data of first row, or null. */ inline fun LitePal.findFirst() = findFirst(T::class.java) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @return A FindExecutor instance. */ inline fun LitePal.findFirstAsync() = findFirstAsync(T::class.java) /** * It is mostly same as [LitePal.findFirst] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with data of first row, or null. */ inline fun LitePal.findFirst(isEager: Boolean) = findFirst(T::class.java, isEager) /** * Basically same as [LitePal.findFirst] but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ inline fun LitePal.findFirstAsync(isEager: Boolean) = findFirstAsync(T::class.java, isEager) /** * Finds the last record of a single table. * * val p = LitePal.findLast<Person>() * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findLast] with isEager parameter. * * @return An object with data of last row, or null. */ inline fun LitePal.findLast() = findLast(T::class.java) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @return A FindExecutor instance. */ inline fun LitePal.findLastAsync() = findLastAsync(T::class.java) /** * It is mostly same as [LitePal.findLast] but an isEager * parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @return An object with data of last row, or null. */ inline fun LitePal.findLast(isEager: Boolean) = findLast(T::class.java, isEager) /** * Basically same as [LitePal.findLast] but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @return A FindExecutor instance. */ inline fun LitePal.findLastAsync(isEager: Boolean) = findLastAsync(T::class.java, isEager) /** * Finds multiple records by an id array. * * val people = LitePal.findAll<Person>(1, 2, 3) * * val bookIds = longArrayOf(10, 18) * * LitePal.findAll<Book>(*bookIds) * * Of course you can find all records by passing nothing to the ids * parameter. * * val allBooks = LitePal.findAll<Book>() * * Note that the associated models won't be loaded by default considering * the efficiency, but you can do that by using * [LitePal.findAll] with isEager parameter. * * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ inline fun LitePal.findAll(vararg ids: Long) = findAll(T::class.java, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ inline fun LitePal.findAllAsync(vararg ids: Long) = findAllAsync(T::class.java, *ids) /** * It is mostly same as [LitePal.findAll] but an * isEager parameter. If set true the associated models will be loaded as well. * * Note that isEager will only work for one deep level relation, considering the query efficiency. * You have to implement on your own if you need to load multiple deepness of relation at once. * * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return An object list with found data from database, or an empty list. */ inline fun LitePal.findAll(isEager: Boolean, vararg ids: Long) = findAll(T::class.java, isEager, *ids) /** * Basically same as [LitePal.findAll] but pending to a new thread for executing. * * @param isEager * True to load the associated models, false not. * @param ids * Which records to query. Or do not pass it to find all records. * @return A FindMultiExecutor instance. */ inline fun LitePal.findAllAsync(isEager: Boolean, vararg ids: Long) = findAllAsync(T::class.java, isEager, *ids) /** * Deletes the record in the database by id. * * The data in other tables which is referenced with the record will be * removed too. * * LitePal.delete<Person>(1) * * This means that the record 1 in person table will be removed. * * @param id * Which record to delete. * @return The number of rows affected. Including cascade delete rows. */ inline fun LitePal.delete(id: Long) = delete(T::class.java, id) /** * Basically same as [LitePal.delete] but pending to a new thread for executing. * * @param id * Which record to delete. * @return A UpdateOrDeleteExecutor instance. */ inline fun LitePal.deleteAsync(id: Long) = deleteAsync(T::class.java, id) /** * Deletes all records with details given if they match a set of conditions * supplied. This method constructs a single SQL DELETE statement and sends * it to the database. * * LitePal.deleteAll<Person>("name = ? and age = ?", "Tom", "14") * * This means that all the records which name is Tom and age is 14 will be * removed. * * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return The number of rows affected. */ inline fun LitePal.deleteAll(vararg conditions: String?) = deleteAll(T::class.java, *conditions) /** * Basically same as [LitePal.deleteAll] but pending to a new thread for executing. * * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * deleting. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will delete * all rows. * @return A UpdateOrDeleteExecutor instance. */ inline fun LitePal.deleteAllAsync(vararg conditions: String?) = deleteAllAsync(T::class.java, *conditions) /** * Updates the corresponding record by id with ContentValues. Returns the * number of affected rows. * * val cv = ContentValues() * * cv.put("name", "Jim") * * LitePal.update<Person>(cv, 1) * * This means that the name of record 1 will be updated into Jim. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return The number of rows affected. */ inline fun LitePal.update(values: ContentValues, id: Long) = update(T::class.java, values, id) /** * Basically same as [LitePal.update] but pending to a new thread for executing. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param id * Which record to update. * @return A UpdateOrDeleteExecutor instance. */ inline fun LitePal.updateAsync(values: ContentValues, id: Long) = updateAsync(T::class.java, values, id) /** * Updates all records with details given if they match a set of conditions * supplied. This method constructs a single SQL UPDATE statement and sends * it to the database. * * val cv = ContentValues() * * cv.put("name", "Jim") * * LitePal.update<Person>(cv, "name = ?", "Tom") * * This means that all the records which name is Tom will be updated into * Jim. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return The number of rows affected. */ inline fun LitePal.updateAll(values: ContentValues, vararg conditions: String?) = updateAll(T::class.java, values, *conditions) /** * Basically same as [LitePal.updateAll] but pending to a new thread for executing. * * @param values * A map from column names to new column values. null is a valid * value that will be translated to NULL. * @param conditions * A string array representing the WHERE part of an SQL * statement. First parameter is the WHERE clause to apply when * updating. The way of specifying place holders is to insert one * or more question marks in the SQL. The first question mark is * replaced by the second element of the array, the next question * mark by the third, and so on. Passing empty string will update * all rows. * @return A UpdateOrDeleteExecutor instance. */ inline fun LitePal.updateAllAsync(values: ContentValues, vararg conditions: String?) = updateAllAsync(T::class.java, values, *conditions) /** * Check if the specified conditions data already exists in the table. * @param conditions * A filter declaring which data to check. Exactly same use as * [LitePal.where], except null conditions will result in false. * @return Return true if the specified conditions data already exists in the table. * False otherwise. Null conditions will result in false. */ inline fun LitePal.isExist(vararg conditions: String?) = isExist(T::class.java, *conditions) ================================================ FILE: kotlin/src/main/res/values/strings.xml ================================================ LitePal ================================================ FILE: sample/.gitignore ================================================ /build ================================================ FILE: sample/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { compileSdkVersion 31 defaultConfig { applicationId "org.litepal.litepalsample" minSdkVersion 15 targetSdkVersion 31 versionCode 1 versionName "1.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { abortOnError false } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' testImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } ================================================ FILE: sample/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/tony/Android/Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Book.java ================================================ package com.litepaltest.model; import org.litepal.crud.LitePalSupport; import java.io.Serializable; public class Book extends LitePalSupport implements Serializable{ private static final long serialVersionUID = 9040804172147110007L; private long id; private String bookName; private Integer pages; private double price; private char level; private short isbn; private boolean isPublished; private float area; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public Integer getPages() { return pages; } public void setPages(Integer pages) { this.pages = pages; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public char getLevel() { return level; } public void setLevel(char level) { this.level = level; } public short getIsbn() { return isbn; } public void setIsbn(short isbn) { this.isbn = isbn; } public boolean isPublished() { return isPublished; } public void setPublished(boolean isPublished) { this.isPublished = isPublished; } public float getArea() { return area; } public void setArea(float area) { this.area = area; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Cellphone.java ================================================ package com.litepaltest.model; import org.litepal.annotation.Column; import org.litepal.crud.LitePalSupport; import java.util.ArrayList; import java.util.List; public class Cellphone extends LitePalSupport { private Long id; @Column(index = true) public String brand; private Character inStock; protected Double price; @Column(unique = true, nullable = false) String serial; @Column(nullable = true, defaultValue = "0.0.0.0") private String mac; @Column(ignore = true) private String uuid; private List messages = new ArrayList<>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public Character getInStock() { return inStock; } public void setInStock(Character inStock) { this.inStock = inStock; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String getSerial() { return serial; } public void setSerial(String serial) { this.serial = serial; } public String getMac() { return mac; } public void setMac(String mac) { this.mac = mac; } public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } public List getMessages() { return messages; } public void setMessages(List messages) { this.messages = messages; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Classroom.java ================================================ package com.litepaltest.model; import org.litepal.crud.LitePalSupport; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class Classroom extends LitePalSupport { private int _id; private String name; private List news = new ArrayList<>(); private List numbers = new ArrayList<>(); private Set studentCollection = new HashSet(); private List teachers = new ArrayList(); /** * @return the _id */ public int get_id() { return _id; } /** * @param _id * the _id to set */ public void set_id(int _id) { this._id = _id; } /** * @return the name */ public String getName() { return name; } /** * @param name * the name to set */ public void setName(String name) { this.name = name; } public Set getStudentCollection() { return studentCollection; } public void setStudentCollection(Set studentCollection) { this.studentCollection = studentCollection; } public List getTeachers() { return teachers; } public void setTeachers(List teachers) { this.teachers = teachers; } public List getNews() { return news; } public void setNews(List news) { this.news = news; } public List getNumbers() { return numbers; } public void setNumbers(List numbers) { this.numbers = numbers; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Computer.java ================================================ package com.litepaltest.model; import org.litepal.crud.LitePalSupport; public class Computer extends LitePalSupport { private long id; private String brand; private double price; public Computer(String brand, double price) { this.brand = brand; this.price = price; } public long getId() { return id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Headset.java ================================================ package com.litepaltest.model; public class Headset { private int id; private String brand; private String color; private int type; private double price; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getType() { return type; } public void setType(int type) { this.type = type; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/IdCard.java ================================================ package com.litepaltest.model; import org.litepal.crud.LitePalSupport; public class IdCard extends LitePalSupport { private int id; private String number; private String address; private Student student; private long serial; // private Teacher teacher; /** * @return the id */ public int getId() { return id; } /** * @param id * the id to set */ public void setId(int id) { this.id = id; } /** * @return the number */ public String getNumber() { return number; } /** * @param number * the number to set */ public void setNumber(String number) { this.number = number; } /** * @return the address */ public String getAddress() { return address; } /** * @param address * the address to set */ public void setAddress(String address) { this.address = address; } /** * @return the student */ public Student getStudent() { return student; } /** * @param student the student to set */ public void setStudent(Student student) { this.student = student; } public long getSerial() { return serial; } public void setSerial(long serial) { this.serial = serial; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Message.java ================================================ package com.litepaltest.model; import org.litepal.annotation.Column; import org.litepal.crud.LitePalSupport; public class Message extends LitePalSupport { private int id; private String content; public int type; @Column(ignore = true) String title; public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getId() { return id; } public void setId(int id) { this.id = id; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Product.java ================================================ package com.litepaltest.model; import org.litepal.crud.LitePalSupport; public class Product extends LitePalSupport{ private int id; private String brand; private double price; private byte[] pic; public Product() { } public Product(Product p) { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public byte[] getPic() { return pic; } public void setPic(byte[] pic) { this.pic = pic; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Student.java ================================================ package com.litepaltest.model; import org.litepal.annotation.Column; import org.litepal.crud.LitePalSupport; import java.util.ArrayList; import java.util.Date; import java.util.List; public class Student extends LitePalSupport { private int id; private String name; private int age; private Date birthday; @Column(defaultValue = "1589203961859") private Date schoolDate; private Classroom classroom; private IdCard idcard; // private Teacher teacher; private List teachers = new ArrayList(); // private IdCard idCard; // private double salary; // private String address; // private Teacher teacher; // private int classroom_id; // private List lists; // private Context context; // private IdCard idCard; // private List classrooms; /** * @return the name */ public String getName() { return name; } /** * @return the classroom */ public Classroom getClassroom() { return classroom; } /** * @param classroom * the classroom to set */ public void setClassroom(Classroom classroom) { this.classroom = classroom; } /** * @return the id */ public int getId() { return id; } /** * @param id * the id to set */ public void setId(int id) { this.id = id; } /** * @param name * the name to set */ public void setName(String name) { this.name = name; } /** * @return the age */ public int getAge() { return age; } /** * @param age * the age to set */ public void setAge(int age) { this.age = age; } /** * @return the birthday */ public Date getBirthday() { return birthday; } /** * @param birthday * the birthday to set */ public void setBirthday(Date birthday) { this.birthday = birthday; } /** * @return the idcard */ public IdCard getIdcard() { return idcard; } /** * @param idcard * the idcard to set */ public void setIdcard(IdCard idcard) { this.idcard = idcard; } public List getTeachers() { return teachers; } public void setTeachers(List teachers) { this.teachers = teachers; } // /** // * @return the idCard // */ // public IdCard getIdCard() { // return idCard; // } // // /** // * @param idCard the idCard to set // */ // public void setIdCard(IdCard idCard) { // this.idCard = idCard; // } public Date getSchoolDate() { return schoolDate; } public void setSchoolDate(Date schoolDate) { this.schoolDate = schoolDate; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/Teacher.java ================================================ package com.litepaltest.model; import org.litepal.crud.LitePalSupport; import java.util.ArrayList; import java.util.List; public class Teacher extends LitePalSupport { private int id; private String teacherName = ""; private boolean sex = true; private int age = 22; private int teachYears; private IdCard idCard; // private Student student; private List students = new ArrayList(); // /** // * @return the students // */ // public List getStudents() { // return students; // } // // /** // * @param students the students to set // */ // public void setStudents(List students) { // this.students = students; // } /** * @return the teacherName */ public String getTeacherName() { return teacherName; } /** * @return the idCard */ public IdCard getIdCard() { return idCard; } /** * @param idCard * the idCard to set */ public void setIdCard(IdCard idCard) { this.idCard = idCard; } /** * @param teacherName * the teacherName to set */ public void setTeacherName(String teacherName) { this.teacherName = teacherName; } /** * @return the age */ public int getAge() { return age; } /** * @param age * the age to set */ public void setAge(int age) { this.age = age; } /** * @return the teachYears */ public int getTeachYears() { return teachYears; } /** * @param teachYears * the teachYears to set */ public void setTeachYears(int teachYears) { this.teachYears = teachYears; } /** * @return the id */ public int getId() { return id; } /** * @param id * the id to set */ public void setId(int id) { this.id = id; } /** * @return the sex */ public boolean isSex() { return sex; } /** * @param sex * the sex to set */ public void setSex(boolean sex) { this.sex = sex; } public List getStudents() { return students; } public void setStudents(List students) { this.students = students; } // private Student student; // private List students = new ArrayList(); // // private IdCard idCard; // public String getName() { // return name; // } // // public void setName(String name) { // this.name = name; // } // // public boolean isSex() { // return sex; // } // // public void setSex(boolean sex) { // this.sex = sex; // } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/WeChatMessage.java ================================================ package com.litepaltest.model; public class WeChatMessage extends Message { private String friend; public String getFriend() { return friend; } public void setFriend(String friend) { this.friend = friend; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/model/WeiboMessage.java ================================================ package com.litepaltest.model; import org.litepal.annotation.Column; public class WeiboMessage extends Message { private String follower; @Column(ignore = true) private int number; private Cellphone cellphone; public String getFollower() { return follower; } public void setFollower(String follower) { this.follower = follower; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public Cellphone getCellphone() { return cellphone; } public void setCellphone(Cellphone cellphone) { this.cellphone = cellphone; } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/LitePalTestCase.java ================================================ package com.litepaltest.test; import java.util.ArrayList; import java.util.List; import org.litepal.tablemanager.Connector; import org.litepal.util.BaseUtility; import org.litepal.util.DBUtility; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import androidx.test.filters.SmallTest; import com.litepaltest.model.Book; import com.litepaltest.model.Cellphone; import com.litepaltest.model.Classroom; import com.litepaltest.model.Computer; import com.litepaltest.model.IdCard; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @SmallTest public class LitePalTestCase { protected void assertM2M(String table1, String table2, long id1, long id2) { assertTrue(isIntermediateDataCorrect(table1, table2, id1, id2)); } protected void assertM2MFalse(String table1, String table2, long id1, long id2) { assertFalse(isIntermediateDataCorrect(table1, table2, id1, id2)); } /** * * @param table1 * Table without foreign key. * @param table2 * Table with foreign key. * @param table1Id * id of table1. * @param table2Id * id of table2. * @return success or failed. */ protected boolean isFKInsertCorrect(String table1, String table2, long table1Id, long table2Id) { SQLiteDatabase db = Connector.getDatabase(); try (Cursor cursor = db.query(table2, null, "id = ?", new String[]{String.valueOf(table2Id)}, null, null, null)) { cursor.moveToFirst(); long fkId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseUtility.changeCase(table1 + "_id"))); return fkId == table1Id; } catch (Exception e) { e.printStackTrace(); return false; } } protected boolean isIntermediateDataCorrect(String table1, String table2, long table1Id, long table2Id) { SQLiteDatabase db = Connector.getDatabase(); Cursor cursor = null; try { String where = table1 + "_id = ? and " + table2 + "_id = ?"; cursor = db.query(DBUtility.getIntermediateTableName(table1, table2), null, where, new String[] { String.valueOf(table1Id), String.valueOf(table2Id) }, null, null, null); return cursor.getCount() == 1; } catch (Exception e) { e.printStackTrace(); return false; } finally { if (cursor != null) { cursor.close(); } } } protected long getForeignKeyValue(String tableWithFK, String tableWithoutFK, long id) { Cursor cursor = Connector.getDatabase().query(tableWithFK, null, "id = ?", new String[]{String.valueOf(id)}, null, null, null); long foreignKeyId = 0; if (cursor.moveToFirst()) { foreignKeyId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseUtility .changeCase(tableWithoutFK + "_id"))); } cursor.close(); return foreignKeyId; } protected boolean isDataExists(String table, long id) { SQLiteDatabase db = Connector.getDatabase(); try (Cursor cursor = db.query(table, null, "id = ?", new String[]{String.valueOf(id)}, null, null, null)) { return cursor.getCount() == 1; } catch (Exception e) { e.printStackTrace(); } return false; } protected String getTableName(Object object) { return DBUtility.getTableNameByClassName(object.getClass().getName()); } protected String getTableName(Class c) { return DBUtility.getTableNameByClassName(c.getName()); } protected int getRowsCount(String tableName) { int count = 0; Cursor c = Connector.getDatabase().query(tableName, null, null, null, null, null, null); count = c.getCount(); c.close(); return count; } protected List getBooks(String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { List books = new ArrayList(); Cursor cursor = Connector.getDatabase().query(getTableName(Book.class), columns, selection, selectionArgs, groupBy, having, orderBy, limit); if (cursor.moveToFirst()) { do { long id = cursor.getLong(cursor.getColumnIndexOrThrow("id")); String bookName = cursor.getString(cursor.getColumnIndexOrThrow("bookname")); Integer pages = null; if (!cursor.isNull(cursor.getColumnIndexOrThrow("pages"))) { pages = cursor.getInt(cursor.getColumnIndexOrThrow("pages")); } double price = cursor.getDouble(cursor.getColumnIndexOrThrow("price")); char level = cursor.getString(cursor.getColumnIndexOrThrow("level")).charAt(0); short isbn = cursor.getShort(cursor.getColumnIndexOrThrow("isbn")); float area = cursor.getFloat(cursor.getColumnIndexOrThrow("area")); boolean isPublished = cursor.getInt(cursor.getColumnIndexOrThrow("ispublished")) == 1; Book book = new Book(); book.setId(id); book.setBookName(bookName); book.setPages(pages); book.setPrice(price); book.setLevel(level); book.setIsbn(isbn); book.setArea(area); book.setPublished(isPublished); books.add(book); } while (cursor.moveToNext()); } cursor.close(); return books; } protected Classroom getClassroom(long id) { Classroom c = null; Cursor cursor = Connector.getDatabase().query(getTableName(Classroom.class), null, "id = ?", new String[] { String.valueOf(id) }, null, null, null); if (cursor.moveToFirst()) { c = new Classroom(); String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); c.setName(name); } cursor.close(); return c; } protected IdCard getIdCard(long id) { IdCard card = null; Cursor cursor = Connector.getDatabase().query(getTableName(IdCard.class), null, "id = ?", new String[] { String.valueOf(id) }, null, null, null); if (cursor.moveToFirst()) { card = new IdCard(); String address = cursor.getString(cursor.getColumnIndexOrThrow("address")); String number = cursor.getString(cursor.getColumnIndexOrThrow("number")); card.setAddress(address); card.setNumber(number); } cursor.close(); return card; } protected Computer getComputer(long id) { Computer computer = null; Cursor cursor = Connector.getDatabase().query(getTableName(Computer.class), null, "id = ?", new String[] { String.valueOf(id) }, null, null, null); if (cursor.moveToFirst()) { computer = new Computer("", 0); double newPrice = cursor.getDouble(cursor.getColumnIndexOrThrow("price")); String brand = cursor.getString(cursor.getColumnIndexOrThrow("brand")); computer.setBrand(brand); computer.setPrice(newPrice); } cursor.close(); return computer; } protected Cellphone getCellPhone(long id) { Cellphone cellPhone = null; Cursor cursor = Connector.getDatabase().query(getTableName(Cellphone.class), null, "id = ?", new String[] { String.valueOf(id) }, null, null, null); if (cursor.moveToFirst()) { cellPhone = new Cellphone(); double newPrice = cursor.getDouble(cursor.getColumnIndexOrThrow("price")); char inStock = cursor.getString(cursor.getColumnIndexOrThrow("instock")).charAt(0); String brand = cursor.getString(cursor.getColumnIndexOrThrow("brand")); cellPhone.setBrand(brand); cellPhone.setInStock(inStock); cellPhone.setPrice(newPrice); } cursor.close(); return cellPhone; } protected Teacher getTeacher(long id) { Teacher teacher = null; Cursor cursor = Connector.getDatabase().query(getTableName(Teacher.class), null, "id = ?", new String[] { String.valueOf(id) }, null, null, null); if (cursor.moveToFirst()) { teacher = new Teacher(); String teacherName = cursor.getString(cursor.getColumnIndexOrThrow("teachername")); int teachYears = cursor.getInt(cursor.getColumnIndexOrThrow("teachyears")); int age = cursor.getInt(cursor.getColumnIndexOrThrow("age")); int sex = cursor.getInt(cursor.getColumnIndexOrThrow("sex")); teacher.setTeacherName(teacherName); teacher.setTeachYears(teachYears); teacher.setAge(age); if (sex == 0) { teacher.setSex(false); } else if (sex == 1) { teacher.setSex(true); } } cursor.close(); return teacher; } protected Student getStudent(long id) { Student student = null; Cursor cursor = Connector.getDatabase().query(getTableName(Student.class), null, "id = ?", new String[] { String.valueOf(id) }, null, null, null); if (cursor.moveToFirst()) { student = new Student(); String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); int age = cursor.getInt(cursor.getColumnIndexOrThrow("age")); student.setName(name); student.setAge(age); } cursor.close(); return student; } protected List getTeachers(int[] ids) { List teachers = new ArrayList<>(); Cursor cursor = Connector.getDatabase().query(getTableName(Teacher.class), null, getWhere(ids), null, null, null, null); if (cursor.moveToFirst()) { Teacher t = new Teacher(); String teacherName = cursor.getString(cursor.getColumnIndexOrThrow("teachername")); int teachYears = cursor.getInt(cursor.getColumnIndexOrThrow("teachyears")); int age = cursor.getInt(cursor.getColumnIndexOrThrow("age")); int sex = cursor.getInt(cursor.getColumnIndexOrThrow("sex")); t.setTeacherName(teacherName); t.setTeachYears(teachYears); t.setAge(age); if (sex == 0) { t.setSex(false); } else if (sex == 1) { t.setSex(true); } teachers.add(t); } cursor.close(); return teachers; } protected List getStudents(int[] ids) { List students = new ArrayList<>(); Cursor cursor = Connector.getDatabase().query(getTableName(Student.class), null, getWhere(ids), null, null, null, null); if (cursor.moveToFirst()) { Student s = new Student(); String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); int age = cursor.getInt(cursor.getColumnIndexOrThrow("age")); s.setName(name); s.setAge(age); students.add(s); } cursor.close(); return students; } private String getWhere(int[] ids) { StringBuilder where = new StringBuilder(); boolean needOr = false; for (int id : ids) { if (needOr) { where.append(" or "); } where.append("id = ").append(id); needOr = true; } return where.toString(); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/MultiDatabaseTest.java ================================================ package com.litepaltest.test; import android.database.sqlite.SQLiteDatabase; import androidx.test.filters.SmallTest; import com.litepaltest.model.Classroom; import com.litepaltest.model.Computer; import com.litepaltest.model.Headset; import com.litepaltest.model.Product; import org.junit.Test; import org.litepal.LitePal; import org.litepal.LitePalDB; import org.litepal.util.DBUtility; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; /** * @author guolin * @since 2016/11/10 */ @SmallTest public class MultiDatabaseTest extends LitePalTestCase { @Test public void testMultiDatabase() { LitePal.deleteDatabase("db2"); SQLiteDatabase db = LitePal.getDatabase(); assertTrue(DBUtility.isTableExists("Album", db)); assertTrue(DBUtility.isTableExists("Song", db)); assertTrue(DBUtility.isTableExists("Singer", db)); assertTrue(DBUtility.isTableExists("Classroom", db)); assertTrue(DBUtility.isTableExists("Teacher", db)); assertTrue(DBUtility.isTableExists("IdCard", db)); assertTrue(DBUtility.isTableExists("Student", db)); assertTrue(DBUtility.isTableExists("Cellphone", db)); assertTrue(DBUtility.isTableExists("Computer", db)); assertTrue(DBUtility.isTableExists("Book", db)); assertTrue(DBUtility.isTableExists("Product", db)); assertTrue(DBUtility.isTableExists("Headset", db)); assertTrue(DBUtility.isTableExists("WeChatMessage", db)); assertTrue(DBUtility.isTableExists("WeiboMessage", db)); LitePalDB litePalDB = new LitePalDB("db2", 1); litePalDB.addClassName(Classroom.class.getName()); litePalDB.addClassName(Product.class.getName()); litePalDB.setExternalStorage(true); LitePal.use(litePalDB); db = LitePal.getDatabase(); assertFalse(DBUtility.isTableExists("Album", db)); assertFalse(DBUtility.isTableExists("Song", db)); assertFalse(DBUtility.isTableExists("Singer", db)); assertTrue(DBUtility.isTableExists("Classroom", db)); assertFalse(DBUtility.isTableExists("Teacher", db)); assertFalse(DBUtility.isTableExists("IdCard", db)); assertFalse(DBUtility.isTableExists("Student", db)); assertFalse(DBUtility.isTableExists("Cellphone", db)); assertFalse(DBUtility.isTableExists("Computer", db)); assertFalse(DBUtility.isTableExists("Book", db)); assertTrue(DBUtility.isTableExists("Product", db)); assertFalse(DBUtility.isTableExists("Headset", db)); assertFalse(DBUtility.isTableExists("WeChatMessage", db)); assertFalse(DBUtility.isTableExists("WeiboMessage", db)); litePalDB = new LitePalDB("db2", 2); litePalDB.addClassName(Computer.class.getName()); litePalDB.addClassName(Product.class.getName()); litePalDB.addClassName(Headset.class.getName()); litePalDB.setExternalStorage(true); LitePal.use(litePalDB); db = LitePal.getDatabase(); assertFalse(DBUtility.isTableExists("Album", db)); assertFalse(DBUtility.isTableExists("Song", db)); assertFalse(DBUtility.isTableExists("Singer", db)); assertFalse(DBUtility.isTableExists("Classroom", db)); assertFalse(DBUtility.isTableExists("Teacher", db)); assertFalse(DBUtility.isTableExists("IdCard", db)); assertFalse(DBUtility.isTableExists("Student", db)); assertFalse(DBUtility.isTableExists("Cellphone", db)); assertTrue(DBUtility.isTableExists("Computer", db)); assertFalse(DBUtility.isTableExists("Book", db)); assertTrue(DBUtility.isTableExists("Product", db)); assertTrue(DBUtility.isTableExists("Headset", db)); assertFalse(DBUtility.isTableExists("WeChatMessage", db)); assertFalse(DBUtility.isTableExists("WeiboMessage", db)); LitePal.useDefault(); db = LitePal.getDatabase(); assertTrue(DBUtility.isTableExists("Album", db)); assertTrue(DBUtility.isTableExists("Song", db)); assertTrue(DBUtility.isTableExists("Singer", db)); assertTrue(DBUtility.isTableExists("Classroom", db)); assertTrue(DBUtility.isTableExists("Teacher", db)); assertTrue(DBUtility.isTableExists("IdCard", db)); assertTrue(DBUtility.isTableExists("Student", db)); assertTrue(DBUtility.isTableExists("Cellphone", db)); assertTrue(DBUtility.isTableExists("Computer", db)); assertTrue(DBUtility.isTableExists("Book", db)); assertTrue(DBUtility.isTableExists("Product", db)); assertTrue(DBUtility.isTableExists("Headset", db)); assertTrue(DBUtility.isTableExists("WeChatMessage", db)); assertTrue(DBUtility.isTableExists("WeiboMessage", db)); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/annotation/ColumnTest.java ================================================ package com.litepaltest.test.annotation; import androidx.test.filters.SmallTest; import com.litepaltest.model.Cellphone; import com.litepaltest.test.LitePalTestCase; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import java.util.UUID; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; /** * Created by tony on 15-8-24. */ @SmallTest public class ColumnTest extends LitePalTestCase { @Before public void setUp() { LitePal.getDatabase(); } @Test public void testUnique() { String serial = UUID.randomUUID().toString(); for (int i = 0; i < 2; i++) { Cellphone cellphone = new Cellphone(); cellphone.setBrand("三星"); cellphone.setInStock('Y'); cellphone.setPrice(1949.99); cellphone.setSerial(serial); if (i == 0) { assertTrue(cellphone.save()); } else if (i == 1) { assertFalse(cellphone.save()); } } } @Test public void testNotNull() { Cellphone cellphone = new Cellphone(); cellphone.setBrand("三星"); cellphone.setInStock('Y'); cellphone.setPrice(1949.99); assertFalse(cellphone.save()); cellphone.setSerial(UUID.randomUUID().toString()); assertTrue(cellphone.save()); } @Test public void testDefaultValue() { Cellphone cellphone = new Cellphone(); cellphone.setBrand("三星"); cellphone.setInStock('Y'); cellphone.setPrice(1949.99); cellphone.setSerial(UUID.randomUUID().toString()); assertTrue(cellphone.save()); assertEquals("0.0.0.0", LitePal.find(Cellphone.class, cellphone.getId()).getMac()); cellphone.setMac("192.168.0.1"); assertTrue(cellphone.save()); assertEquals("192.168.0.1", LitePal.find(Cellphone.class, cellphone.getId()).getMac()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/delete/DeleteKotlinTest.kt ================================================ package com.litepaltest.test.crud.delete import android.database.sqlite.SQLiteException import androidx.test.filters.SmallTest import com.litepaltest.model.Classroom import com.litepaltest.model.IdCard import com.litepaltest.model.Student import com.litepaltest.model.Teacher import com.litepaltest.test.LitePalTestCase import junit.framework.TestCase.* import org.junit.Before import org.junit.Test import org.litepal.LitePal import org.litepal.exceptions.DataSupportException import org.litepal.extension.delete import org.litepal.extension.deleteAll import org.litepal.extension.find import org.litepal.util.DBUtility import java.util.* @SmallTest class DeleteKotlinTest : LitePalTestCase() { private var gameRoom: Classroom? = null private var jude: Student? = null private var rose: Student? = null private var john: Teacher? = null private var mike: Teacher? = null private var judeCard: IdCard? = null private var roseCard: IdCard? = null private var johnCard: IdCard? = null private var mikeCard: IdCard? = null private var studentTable: String? = null private var teacherTable: String? = null @Before fun setUp() { studentTable = DBUtility.getTableNameByClassName(Student::class.java.name) teacherTable = DBUtility.getTableNameByClassName(Teacher::class.java.name) } @Test fun createClassroomStudentsTeachers() { initGameRoom() initRose() initJude() initMike() initJohn() val students = HashSet() students.add(rose!!) students.add(jude!!) gameRoom!!.studentCollection = students gameRoom!!.teachers.add(john) gameRoom!!.teachers.add(mike) gameRoom!!.save() rose!!.save() jude!!.save() john!!.save() mike!!.save() } @Test fun createStudentsTeachersWithIdCard() { initRose() initJude() initMike() initJohn() rose!!.save() jude!!.save() mike!!.save() john!!.save() roseCard!!.save() judeCard!!.save() mikeCard!!.save() johnCard!!.save() } @Test fun createStudentsTeachersWithAssociations() { initRose() initJude() initMike() initJohn() rose!!.teachers.add(john) rose!!.teachers.add(mike) jude!!.teachers.add(mike) rose!!.save() jude!!.save() john!!.save() mike!!.save() } @Test fun testDeleteWithNoParameter() { initJude() jude!!.save() val rowsAffected = jude!!.delete() assertEquals(1, rowsAffected) val s = getStudent(jude!!.id.toLong()) assertNull(s) } @Test fun testDeleteById() { initJude() jude!!.save() val rowsAffected = LitePal.delete(jude!!.id.toLong()) assertEquals(1, rowsAffected) val s = getStudent(jude!!.id.toLong()) assertNull(s) } @Test fun testDeleteNoSavedModelWithNoParameter() { val tony = Student() tony.name = "Tony" tony.age = 23 val rowsAffected = tony.delete() assertEquals(0, rowsAffected) } @Test fun testDeleteWithNotExistsRecordById() { val rowsAffected = LitePal.delete(998909) assertEquals(0, rowsAffected) } @Test fun testDeleteCascadeM2OAssociationsOnMSideWithNoParameter() { createClassroomStudentsTeachers() val rowsAffected = gameRoom!!.delete() assertEquals(5, rowsAffected) assertNull(getClassroom(gameRoom!!._id.toLong())) assertNull(getStudent(jude!!.id.toLong())) assertNull(getStudent(rose!!.id.toLong())) assertNull(getTeacher(john!!.id.toLong())) assertNull(getTeacher(mike!!.id.toLong())) } @Test fun testDeleteCascadeM2OAssociationsOnMSideById() { createClassroomStudentsTeachers() val rowsAffected = LitePal.delete(gameRoom!!._id.toLong()) assertEquals(5, rowsAffected) assertNull(getClassroom(gameRoom!!._id.toLong())) assertNull(getStudent(jude!!.id.toLong())) assertNull(getStudent(rose!!.id.toLong())) assertNull(getTeacher(john!!.id.toLong())) assertNull(getTeacher(mike!!.id.toLong())) } @Test fun testDeleteAllCascadeM2OAssociationsOnMSide() { createClassroomStudentsTeachers() val rowsAffected = LitePal.deleteAll("id = ?", gameRoom!!._id.toString() + "") assertEquals(5, rowsAffected) assertNull(getClassroom(gameRoom!!._id.toLong())) assertNull(getStudent(jude!!.id.toLong())) assertNull(getStudent(rose!!.id.toLong())) assertNull(getTeacher(john!!.id.toLong())) assertNull(getTeacher(mike!!.id.toLong())) } @Test fun testDeleteCascadeM2OAssociationsOnOSideWithNoParameter() { createClassroomStudentsTeachers() var rowsAffected = jude!!.delete() assertEquals(1, rowsAffected) assertNull(getStudent(jude!!.id.toLong())) rowsAffected = rose!!.delete() assertEquals(1, rowsAffected) assertNull(getStudent(rose!!.id.toLong())) rowsAffected = john!!.delete() assertEquals(1, rowsAffected) assertNull(getTeacher(john!!.id.toLong())) rowsAffected = mike!!.delete() assertEquals(1, rowsAffected) assertNull(getTeacher(mike!!.id.toLong())) } @Test fun testDeleteCascadeM2OAssociationsOnOSideById() { createClassroomStudentsTeachers() var rowsAffected = LitePal.delete(jude!!.id.toLong()) assertEquals(1, rowsAffected) assertNull(getStudent(jude!!.id.toLong())) rowsAffected = LitePal.delete(rose!!.id.toLong()) assertEquals(1, rowsAffected) assertNull(getStudent(rose!!.id.toLong())) rowsAffected = LitePal.delete(john!!.id.toLong()) assertEquals(1, rowsAffected) assertNull(getTeacher(john!!.id.toLong())) rowsAffected = LitePal.delete(mike!!.id.toLong()) assertEquals(1, rowsAffected) assertNull(getTeacher(mike!!.id.toLong())) } @Test fun testDeleteAllCascadeM2OAssociationsOnOSide() { createClassroomStudentsTeachers() var rowsAffected = LitePal.deleteAll("id = ?", jude!!.id.toString()) assertEquals(1, rowsAffected) assertNull(getStudent(jude!!.id.toLong())) rowsAffected = LitePal.deleteAll("id = ?", rose!!.id.toString()) assertEquals(1, rowsAffected) assertNull(getStudent(rose!!.id.toLong())) rowsAffected = LitePal.deleteAll("id = ?", john!!.id.toString()) assertEquals(1, rowsAffected) assertNull(getTeacher(john!!.id.toLong())) rowsAffected = LitePal.deleteAll("id = ?", mike!!.id.toString()) assertEquals(1, rowsAffected) assertNull(getTeacher(mike!!.id.toLong())) } @Test fun testDeleteCascadeO2OAssociationsWithNoParameter() { createStudentsTeachersWithIdCard() var affectedRows = jude!!.delete() assertEquals(2, affectedRows) assertNull(getStudent(jude!!.id.toLong())) assertNull(getIdCard(judeCard!!.id.toLong())) affectedRows = roseCard!!.delete() assertEquals(2, affectedRows) assertNull(getStudent(rose!!.id.toLong())) assertNull(getIdCard(roseCard!!.id.toLong())) affectedRows = john!!.delete() assertEquals(2, affectedRows) assertNull(getTeacher(john!!.id.toLong())) assertNull(getIdCard(johnCard!!.id.toLong())) affectedRows = mikeCard!!.delete() assertEquals(1, affectedRows) assertNull(getIdCard(mikeCard!!.id.toLong())) } @Test fun testDeleteCascadeO2OAssociationsById() { createStudentsTeachersWithIdCard() var affectedRows = LitePal.delete(jude!!.id.toLong()) assertEquals(2, affectedRows) assertNull(getStudent(jude!!.id.toLong())) assertNull(getIdCard(judeCard!!.id.toLong())) affectedRows = LitePal.delete(roseCard!!.id.toLong()) assertEquals(2, affectedRows) assertNull(getStudent(rose!!.id.toLong())) assertNull(getIdCard(roseCard!!.id.toLong())) affectedRows = LitePal.delete(john!!.id.toLong()) assertEquals(2, affectedRows) assertNull(getTeacher(john!!.id.toLong())) assertNull(getIdCard(johnCard!!.id.toLong())) affectedRows = LitePal.delete(mikeCard!!.id.toLong()) assertEquals(1, affectedRows) assertNull(getIdCard(mikeCard!!.id.toLong())) } @Test fun testDeleteAllCascadeO2OAssociations() { createStudentsTeachersWithIdCard() var affectedRows = LitePal.deleteAll("id = ?", jude!!.id.toString()) assertEquals(2, affectedRows) assertNull(getStudent(jude!!.id.toLong())) assertNull(getIdCard(judeCard!!.id.toLong())) affectedRows = LitePal.deleteAll("id = ?", roseCard!!.id.toString() + "") assertEquals(2, affectedRows) assertNull(getStudent(rose!!.id.toLong())) assertNull(getIdCard(roseCard!!.id.toLong())) affectedRows = LitePal.deleteAll("id = ?", "" + john!!.id) assertEquals(2, affectedRows) assertNull(getTeacher(john!!.id.toLong())) assertNull(getIdCard(johnCard!!.id.toLong())) affectedRows = LitePal.deleteAll("id=?", "" + mikeCard!!.id) assertEquals(1, affectedRows) assertNull(getIdCard(mikeCard!!.id.toLong())) } @Test fun testDeleteCascadeM2MAssociationsWithNoParameter() { createStudentsTeachersWithAssociations() var rowsAffected = jude!!.delete() assertEquals(2, rowsAffected) assertNull(getStudent(jude!!.id.toLong())) assertM2MFalse(studentTable, teacherTable, jude!!.id.toLong(), mike!!.id.toLong()) assertM2M(studentTable, teacherTable, rose!!.id.toLong(), mike!!.id.toLong()) assertM2M(studentTable, teacherTable, rose!!.id.toLong(), john!!.id.toLong()) createStudentsTeachersWithAssociations() rowsAffected = rose!!.delete() assertEquals(3, rowsAffected) assertNull(getStudent(rose!!.id.toLong())) assertM2MFalse(studentTable, teacherTable, rose!!.id.toLong(), mike!!.id.toLong()) assertM2MFalse(studentTable, teacherTable, rose!!.id.toLong(), john!!.id.toLong()) assertM2M(studentTable, teacherTable, jude!!.id.toLong(), mike!!.id.toLong()) } @Test fun testDeleteCascadeM2MAssociationsById() { createStudentsTeachersWithAssociations() var rowsAffected = LitePal.delete(john!!.id.toLong()) assertEquals(2, rowsAffected) assertNull(getTeacher(john!!.id.toLong())) assertM2MFalse(studentTable, teacherTable, rose!!.id.toLong(), john!!.id.toLong()) assertM2M(studentTable, teacherTable, rose!!.id.toLong(), mike!!.id.toLong()) assertM2M(studentTable, teacherTable, jude!!.id.toLong(), mike!!.id.toLong()) createStudentsTeachersWithAssociations() rowsAffected = LitePal.delete(mike!!.id.toLong()) assertEquals(3, rowsAffected) assertNull(getTeacher(mike!!.id.toLong())) assertM2MFalse(studentTable, teacherTable, rose!!.id.toLong(), mike!!.id.toLong()) assertM2MFalse(studentTable, teacherTable, jude!!.id.toLong(), mike!!.id.toLong()) assertM2M(studentTable, teacherTable, rose!!.id.toLong(), john!!.id.toLong()) } @Test fun testDeleteAllCascadeM2MAssociations() { createStudentsTeachersWithAssociations() var rowsAffected = LitePal.deleteAll("id=?", "" + john!!.id) assertEquals(2, rowsAffected) assertNull(getTeacher(john!!.id.toLong())) assertM2MFalse(studentTable, teacherTable, rose!!.id.toLong(), john!!.id.toLong()) assertM2M(studentTable, teacherTable, rose!!.id.toLong(), mike!!.id.toLong()) assertM2M(studentTable, teacherTable, jude!!.id.toLong(), mike!!.id.toLong()) createStudentsTeachersWithAssociations() rowsAffected = LitePal.deleteAll("id=?", "" + mike!!.id) assertEquals(3, rowsAffected) assertNull(getTeacher(mike!!.id.toLong())) assertM2MFalse(studentTable, teacherTable, rose!!.id.toLong(), mike!!.id.toLong()) assertM2MFalse(studentTable, teacherTable, jude!!.id.toLong(), mike!!.id.toLong()) assertM2M(studentTable, teacherTable, rose!!.id.toLong(), john!!.id.toLong()) } @Test fun testDeleteAllCascadeWithConditions() { val classroom = Classroom() classroom.name = "1" + System.currentTimeMillis() classroom.save() val classroom2 = Classroom() classroom2.name = "2" + System.currentTimeMillis() classroom2.save() val s1 = Student() s1.classroom = classroom s1.save() val s2 = Student() s2.classroom = classroom s2.save() val s3 = Student() s3.classroom = classroom2 s3.save() var rows = LitePal.deleteAll("name = ?", classroom.name) assertEquals(3, rows) assertNull(getClassroom(classroom._id.toLong())) assertNull(getStudent(s1.id.toLong())) assertNull(getStudent(s2.id.toLong())) assertNotNull(getClassroom(classroom2._id.toLong())) assertNotNull(getStudent(s3.id.toLong())) rows = LitePal.deleteAll("name = ?", classroom2.name) assertEquals(2, rows) assertNull(getClassroom(classroom2._id.toLong())) assertNull(getStudent(s3.id.toLong())) } @Test fun testDeleteAll() { var s: Student val ids = IntArray(5) for (i in 0..4) { s = Student() s.name = "Dusting" s.age = i + 10086 s.save() ids[i] = s.id } var affectedRows = LitePal.deleteAll("name = ? and age = ?", "Dusting", "10088") assertEquals(1, affectedRows) assertNull(getStudent(ids[2].toLong())) affectedRows = LitePal.deleteAll("name = ? and age > ? and age < ?", "Dusting", "10085", "10092") assertEquals(4, affectedRows) } @Test fun testDeleteAllRows() { createStudentsTeachersWithIdCard() var rowsCount = getRowsCount(teacherTable) var affectedRows = LitePal.deleteAll() assertTrue(rowsCount <= affectedRows) rowsCount = getRowsCount(studentTable) affectedRows = LitePal.deleteAll() assertTrue(rowsCount <= affectedRows) rowsCount = getRowsCount(DBUtility.getTableNameByClassName(IdCard::class.java.name)) affectedRows = LitePal.deleteAll() assertTrue(rowsCount <= affectedRows) createStudentsTeachersWithAssociations() rowsCount = getRowsCount(teacherTable) affectedRows = LitePal.deleteAll() assertTrue(rowsCount <= affectedRows) rowsCount = getRowsCount(studentTable) affectedRows = LitePal.deleteAll() assertTrue(rowsCount <= affectedRows) rowsCount = getRowsCount(DBUtility.getIntermediateTableName(studentTable, teacherTable)) affectedRows = LitePal.deleteAll(DBUtility.getIntermediateTableName(studentTable, teacherTable)) assertTrue(rowsCount <= affectedRows) } @Test fun testMarkAsDeleted() { val students = ArrayList() for (i in 0..4) { val s = Student() s.name = "Dusting" s.age = i + 10 students.add(s) } LitePal.saveAll(students) var list = LitePal.where("name=?", "Dusting").find() assertTrue(list.size >= 5) LitePal.deleteAll(Student::class.java, "name=?", "Dusting") list = LitePal.where("name=?", "Dusting").find() assertEquals(0, list.size) LitePal.saveAll(students) list = LitePal.where("name=?", "Dusting").find() assertEquals(0, list.size) LitePal.markAsDeleted(students) LitePal.saveAll(students) list = LitePal.where("name=?", "Dusting").find() assertEquals(5, list.size) } @Test fun testDeleteAllWithWrongConditions() { try { LitePal.deleteAll("name = 'Dustin'", "aaa") fail() } catch (e: DataSupportException) { assertEquals("The parameters in conditions are incorrect.", e.message) } try { LitePal.deleteAll(null, null) fail() } catch (e: DataSupportException) { assertEquals("The parameters in conditions are incorrect.", e.message) } try { LitePal.deleteAll("address = ?", "HK") fail() } catch (e: SQLiteException) { } } @Test fun testDeleteWithGenericData() { val classroom = Classroom() classroom.name = "classroom1" classroom.news.add("news1") classroom.news.add("news2") classroom.news.add("news3") classroom.save() val id = classroom._id val tableName = DBUtility.getGenericTableName(Classroom::class.java.name, "news") val column = DBUtility.getGenericValueIdColumnName(Classroom::class.java.name) var c = LitePal.findBySQL("select * from $tableName where $column = ?", id.toString()) assertEquals(3, c!!.count) c.close() classroom.delete() c = LitePal.findBySQL("select * from $tableName where $column = ?", id.toString()) assertEquals(0, c!!.count) c.close() assertFalse(classroom.isSaved) classroom.save() assertTrue(classroom.isSaved) c = LitePal.findBySQL("select * from $tableName where $column = ?", classroom._id.toString()) assertEquals(3, c!!.count) c.close() LitePal.deleteAll("id = ?", classroom._id.toString()) c = LitePal.findBySQL("select * from $tableName where $column = ?", classroom._id.toString()) assertEquals(0, c!!.count) c.close() } private fun initGameRoom() { gameRoom = Classroom() gameRoom!!.name = "Game room" } private fun initJude() { jude = Student() jude!!.name = "Jude" jude!!.age = 13 judeCard = IdCard() judeCard!!.address = "Jude Street" judeCard!!.number = "123456" jude!!.idcard = judeCard judeCard!!.student = jude } private fun initRose() { rose = Student() rose!!.name = "Rose" rose!!.age = 15 roseCard = IdCard() roseCard!!.address = "Rose Street" roseCard!!.number = "123457" roseCard!!.student = rose } private fun initJohn() { john = Teacher() john!!.teacherName = "John" john!!.age = 33 john!!.teachYears = 13 johnCard = IdCard() johnCard!!.address = "John Street" johnCard!!.number = "123458" john!!.idCard = johnCard } private fun initMike() { mike = Teacher() mike!!.teacherName = "Mike" mike!!.age = 36 mike!!.teachYears = 16 mikeCard = IdCard() mikeCard!!.address = "Mike Street" mikeCard!!.number = "123459" mike!!.idCard = mikeCard } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/delete/DeleteTest.java ================================================ package com.litepaltest.test.crud.delete; import android.database.Cursor; import android.database.sqlite.SQLiteException; import androidx.test.filters.SmallTest; import com.litepaltest.model.Classroom; import com.litepaltest.model.IdCard; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import com.litepaltest.test.LitePalTestCase; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import org.litepal.exceptions.DataSupportException; import org.litepal.util.DBUtility; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; @SmallTest public class DeleteTest extends LitePalTestCase { private Classroom gameRoom; private Student jude; private Student rose; private Teacher john; private Teacher mike; private IdCard judeCard; private IdCard roseCard; private IdCard johnCard; private IdCard mikeCard; private String studentTable; private String teacherTable; @Before public void setUp() { studentTable = DBUtility.getTableNameByClassName(Student.class.getName()); teacherTable = DBUtility.getTableNameByClassName(Teacher.class.getName()); } @Test public void createClassroomStudentsTeachers() { initGameRoom(); initRose(); initJude(); initMike(); initJohn(); Set students = new HashSet(); students.add(rose); students.add(jude); gameRoom.setStudentCollection(students); gameRoom.getTeachers().add(john); gameRoom.getTeachers().add(mike); gameRoom.save(); rose.save(); jude.save(); john.save(); mike.save(); } @Test public void createStudentsTeachersWithIdCard() { initRose(); initJude(); initMike(); initJohn(); rose.save(); jude.save(); mike.save(); john.save(); roseCard.save(); judeCard.save(); mikeCard.save(); johnCard.save(); } @Test public void createStudentsTeachersWithAssociations() { initRose(); initJude(); initMike(); initJohn(); rose.getTeachers().add(john); rose.getTeachers().add(mike); jude.getTeachers().add(mike); rose.save(); jude.save(); john.save(); mike.save(); } @Test public void testDeleteWithNoParameter() { initJude(); jude.save(); int rowsAffected = jude.delete(); assertEquals(1, rowsAffected); Student s = getStudent(jude.getId()); assertNull(s); } @Test public void testDeleteById() { initJude(); jude.save(); int rowsAffected = LitePal.delete(Student.class, jude.getId()); assertEquals(1, rowsAffected); Student s = getStudent(jude.getId()); assertNull(s); } @Test public void testDeleteNoSavedModelWithNoParameter() { Student tony = new Student(); tony.setName("Tony"); tony.setAge(23); int rowsAffected = tony.delete(); assertEquals(0, rowsAffected); } @Test public void testDeleteWithNotExistsRecordById() { int rowsAffected = LitePal.delete(Student.class, 998909); assertEquals(0, rowsAffected); } @Test public void testDeleteCascadeM2OAssociationsOnMSideWithNoParameter() { createClassroomStudentsTeachers(); int rowsAffected = gameRoom.delete(); assertEquals(5, rowsAffected); assertNull(getClassroom(gameRoom.get_id())); assertNull(getStudent(jude.getId())); assertNull(getStudent(rose.getId())); assertNull(getTeacher(john.getId())); assertNull(getTeacher(mike.getId())); } @Test public void testDeleteCascadeM2OAssociationsOnMSideById() { createClassroomStudentsTeachers(); int rowsAffected = LitePal.delete(Classroom.class, gameRoom.get_id()); assertEquals(5, rowsAffected); assertNull(getClassroom(gameRoom.get_id())); assertNull(getStudent(jude.getId())); assertNull(getStudent(rose.getId())); assertNull(getTeacher(john.getId())); assertNull(getTeacher(mike.getId())); } @Test public void testDeleteAllCascadeM2OAssociationsOnMSide() { createClassroomStudentsTeachers(); int rowsAffected = LitePal.deleteAll(Classroom.class, "id = ?", gameRoom.get_id() + ""); assertEquals(5, rowsAffected); assertNull(getClassroom(gameRoom.get_id())); assertNull(getStudent(jude.getId())); assertNull(getStudent(rose.getId())); assertNull(getTeacher(john.getId())); assertNull(getTeacher(mike.getId())); } @Test public void testDeleteCascadeM2OAssociationsOnOSideWithNoParameter() { createClassroomStudentsTeachers(); int rowsAffected = jude.delete(); assertEquals(1, rowsAffected); assertNull(getStudent(jude.getId())); rowsAffected = rose.delete(); assertEquals(1, rowsAffected); assertNull(getStudent(rose.getId())); rowsAffected = john.delete(); assertEquals(1, rowsAffected); assertNull(getTeacher(john.getId())); rowsAffected = mike.delete(); assertEquals(1, rowsAffected); assertNull(getTeacher(mike.getId())); } @Test public void testDeleteCascadeM2OAssociationsOnOSideById() { createClassroomStudentsTeachers(); int rowsAffected = LitePal.delete(Student.class, jude.getId()); assertEquals(1, rowsAffected); assertNull(getStudent(jude.getId())); rowsAffected = LitePal.delete(Student.class, rose.getId()); assertEquals(1, rowsAffected); assertNull(getStudent(rose.getId())); rowsAffected = LitePal.delete(Teacher.class, john.getId()); assertEquals(1, rowsAffected); assertNull(getTeacher(john.getId())); rowsAffected = LitePal.delete(Teacher.class, mike.getId()); assertEquals(1, rowsAffected); assertNull(getTeacher(mike.getId())); } @Test public void testDeleteAllCascadeM2OAssociationsOnOSide() { createClassroomStudentsTeachers(); int rowsAffected = LitePal.deleteAll(Student.class, "id = ?", String.valueOf(jude.getId())); assertEquals(1, rowsAffected); assertNull(getStudent(jude.getId())); rowsAffected = LitePal.deleteAll(Student.class, "id = ?", String.valueOf(rose.getId())); assertEquals(1, rowsAffected); assertNull(getStudent(rose.getId())); rowsAffected = LitePal.deleteAll(Teacher.class, "id = ?", String.valueOf(john.getId())); assertEquals(1, rowsAffected); assertNull(getTeacher(john.getId())); rowsAffected = LitePal.deleteAll(Teacher.class, "id = ?", String.valueOf(mike.getId())); assertEquals(1, rowsAffected); assertNull(getTeacher(mike.getId())); } @Test public void testDeleteCascadeO2OAssociationsWithNoParameter() { createStudentsTeachersWithIdCard(); int affectedRows = jude.delete(); assertEquals(2, affectedRows); assertNull(getStudent(jude.getId())); assertNull(getIdCard(judeCard.getId())); affectedRows = roseCard.delete(); assertEquals(2, affectedRows); assertNull(getStudent(rose.getId())); assertNull(getIdCard(roseCard.getId())); affectedRows = john.delete(); assertEquals(2, affectedRows); assertNull(getTeacher(john.getId())); assertNull(getIdCard(johnCard.getId())); affectedRows = mikeCard.delete(); assertEquals(1, affectedRows); assertNull(getIdCard(mikeCard.getId())); } @Test public void testDeleteCascadeO2OAssociationsById() { createStudentsTeachersWithIdCard(); int affectedRows = LitePal.delete(Student.class, jude.getId()); assertEquals(2, affectedRows); assertNull(getStudent(jude.getId())); assertNull(getIdCard(judeCard.getId())); affectedRows = LitePal.delete(IdCard.class, roseCard.getId()); assertEquals(2, affectedRows); assertNull(getStudent(rose.getId())); assertNull(getIdCard(roseCard.getId())); affectedRows = LitePal.delete(Teacher.class, john.getId()); assertEquals(2, affectedRows); assertNull(getTeacher(john.getId())); assertNull(getIdCard(johnCard.getId())); affectedRows = LitePal.delete(IdCard.class, mikeCard.getId()); assertEquals(1, affectedRows); assertNull(getIdCard(mikeCard.getId())); } @Test public void testDeleteAllCascadeO2OAssociations() { createStudentsTeachersWithIdCard(); int affectedRows = LitePal.deleteAll(Student.class, "id = ?", String.valueOf(jude.getId())); assertEquals(2, affectedRows); assertNull(getStudent(jude.getId())); assertNull(getIdCard(judeCard.getId())); affectedRows = LitePal.deleteAll(IdCard.class, "id = ?", roseCard.getId() + ""); assertEquals(2, affectedRows); assertNull(getStudent(rose.getId())); assertNull(getIdCard(roseCard.getId())); affectedRows = LitePal.deleteAll(Teacher.class, "id = ?", "" + john.getId()); assertEquals(2, affectedRows); assertNull(getTeacher(john.getId())); assertNull(getIdCard(johnCard.getId())); affectedRows = LitePal.deleteAll(IdCard.class, "id=?", "" + mikeCard.getId()); assertEquals(1, affectedRows); assertNull(getIdCard(mikeCard.getId())); } @Test public void testDeleteCascadeM2MAssociationsWithNoParameter() { createStudentsTeachersWithAssociations(); int rowsAffected = jude.delete(); assertEquals(2, rowsAffected); assertNull(getStudent(jude.getId())); assertM2MFalse(studentTable, teacherTable, jude.getId(), mike.getId()); assertM2M(studentTable, teacherTable, rose.getId(), mike.getId()); assertM2M(studentTable, teacherTable, rose.getId(), john.getId()); createStudentsTeachersWithAssociations(); rowsAffected = rose.delete(); assertEquals(3, rowsAffected); assertNull(getStudent(rose.getId())); assertM2MFalse(studentTable, teacherTable, rose.getId(), mike.getId()); assertM2MFalse(studentTable, teacherTable, rose.getId(), john.getId()); assertM2M(studentTable, teacherTable, jude.getId(), mike.getId()); } @Test public void testDeleteCascadeM2MAssociationsById() { createStudentsTeachersWithAssociations(); int rowsAffected = LitePal.delete(Teacher.class, john.getId()); assertEquals(2, rowsAffected); assertNull(getTeacher(john.getId())); assertM2MFalse(studentTable, teacherTable, rose.getId(), john.getId()); assertM2M(studentTable, teacherTable, rose.getId(), mike.getId()); assertM2M(studentTable, teacherTable, jude.getId(), mike.getId()); createStudentsTeachersWithAssociations(); rowsAffected = LitePal.delete(Teacher.class, mike.getId()); assertEquals(3, rowsAffected); assertNull(getTeacher(mike.getId())); assertM2MFalse(studentTable, teacherTable, rose.getId(), mike.getId()); assertM2MFalse(studentTable, teacherTable, jude.getId(), mike.getId()); assertM2M(studentTable, teacherTable, rose.getId(), john.getId()); } @Test public void testDeleteAllCascadeM2MAssociations() { createStudentsTeachersWithAssociations(); int rowsAffected = LitePal.deleteAll(Teacher.class, "id=?", "" + john.getId()); assertEquals(2, rowsAffected); assertNull(getTeacher(john.getId())); assertM2MFalse(studentTable, teacherTable, rose.getId(), john.getId()); assertM2M(studentTable, teacherTable, rose.getId(), mike.getId()); assertM2M(studentTable, teacherTable, jude.getId(), mike.getId()); createStudentsTeachersWithAssociations(); rowsAffected = LitePal.deleteAll(Teacher.class, "id=?", "" + mike.getId()); assertEquals(3, rowsAffected); assertNull(getTeacher(mike.getId())); assertM2MFalse(studentTable, teacherTable, rose.getId(), mike.getId()); assertM2MFalse(studentTable, teacherTable, jude.getId(), mike.getId()); assertM2M(studentTable, teacherTable, rose.getId(), john.getId()); } @Test public void testDeleteAllCascadeWithConditions() { Classroom classroom = new Classroom(); classroom.setName("1"+System.currentTimeMillis()); classroom.save(); Classroom classroom2 = new Classroom(); classroom2.setName("2"+ System.currentTimeMillis()); classroom2.save(); Student s1 = new Student(); s1.setClassroom(classroom); s1.save(); Student s2 = new Student(); s2.setClassroom(classroom); s2.save(); Student s3 = new Student(); s3.setClassroom(classroom2); s3.save(); int rows = LitePal.deleteAll(Classroom.class, "name = ?", classroom.getName()); assertEquals(3, rows); assertNull(getClassroom(classroom.get_id())); assertNull(getStudent(s1.getId())); assertNull(getStudent(s2.getId())); assertNotNull(getClassroom(classroom2.get_id())); assertNotNull(getStudent(s3.getId())); rows = LitePal.deleteAll(Classroom.class, "name = ?", classroom2.getName()); assertEquals(2, rows); assertNull(getClassroom(classroom2.get_id())); assertNull(getStudent(s3.getId())); } @Test public void testDeleteAll() { Student s; int[] ids = new int[5]; for (int i = 0; i < 5; i++) { s = new Student(); s.setName("Dusting"); s.setAge(i + 10086); s.save(); ids[i] = s.getId(); } int affectedRows = LitePal.deleteAll(Student.class, "name = ? and age = ?", "Dusting", "10088"); assertEquals(1, affectedRows); assertNull(getStudent(ids[2])); affectedRows = LitePal.deleteAll(Student.class, "name = ? and age > ? and age < ?", "Dusting", "10085", "10092"); assertEquals(4, affectedRows); } @Test public void testDeleteAllRows() { createStudentsTeachersWithIdCard(); int rowsCount = getRowsCount(teacherTable); int affectedRows = 0; affectedRows = LitePal.deleteAll(Teacher.class); assertTrue(rowsCount <= affectedRows); rowsCount = getRowsCount(studentTable); affectedRows = LitePal.deleteAll(Student.class); assertTrue(rowsCount<= affectedRows); rowsCount = getRowsCount(DBUtility.getTableNameByClassName(IdCard.class.getName())); affectedRows = LitePal.deleteAll(IdCard.class); assertTrue(rowsCount<=affectedRows); createStudentsTeachersWithAssociations(); rowsCount = getRowsCount(teacherTable); affectedRows = LitePal.deleteAll(Teacher.class); assertTrue(rowsCount<=affectedRows); rowsCount = getRowsCount(studentTable); affectedRows = LitePal.deleteAll(Student.class); assertTrue(rowsCount<=affectedRows); rowsCount = getRowsCount(DBUtility.getIntermediateTableName(studentTable, teacherTable)); affectedRows = LitePal.deleteAll(DBUtility.getIntermediateTableName(studentTable, teacherTable)); assertTrue(rowsCount<=affectedRows); } @Test public void testMarkAsDeleted() { List students = new ArrayList<>(); for (int i = 0; i < 5; i++) { Student s = new Student(); s.setName("Dusting"); s.setAge(i + 10); students.add(s); } LitePal.saveAll(students); List list = LitePal.where("name=?", "Dusting").find(Student.class); assertTrue(list.size() >= 5); LitePal.deleteAll(Student.class, "name=?", "Dusting"); list = LitePal.where("name=?", "Dusting").find(Student.class); assertEquals(0, list.size()); LitePal.saveAll(students); list = LitePal.where("name=?", "Dusting").find(Student.class); assertEquals(0, list.size()); LitePal.markAsDeleted(students); LitePal.saveAll(students); list = LitePal.where("name=?", "Dusting").find(Student.class); assertEquals(5, list.size()); } @Test public void testDeleteAllWithWrongConditions() { try { LitePal.deleteAll(Student.class, "name = 'Dustin'", "aaa"); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } try { LitePal.deleteAll(Student.class, null, null); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } try { LitePal.deleteAll(Student.class, "address = ?", "HK"); fail(); } catch (SQLiteException e) { } } @Test public void testDeleteWithGenericData() { Classroom classroom = new Classroom(); classroom.setName("classroom1"); classroom.getNews().add("news1"); classroom.getNews().add("news2"); classroom.getNews().add("news3"); classroom.save(); int id = classroom.get_id(); String tableName = DBUtility.getGenericTableName(Classroom.class.getName(), "news"); String column = DBUtility.getGenericValueIdColumnName(Classroom.class.getName()); Cursor c = LitePal.findBySQL("select * from " + tableName + " where " + column + " = ?", String.valueOf(id)); assertEquals(3, c.getCount()); c.close(); classroom.delete(); c = LitePal.findBySQL("select * from " + tableName + " where " + column + " = ?", String.valueOf(id)); assertEquals(0, c.getCount()); c.close(); assertFalse(classroom.isSaved()); classroom.save(); assertTrue(classroom.isSaved()); c = LitePal.findBySQL("select * from " + tableName + " where " + column + " = ?", String.valueOf(classroom.get_id())); assertEquals(3, c.getCount()); c.close(); LitePal.deleteAll(Classroom.class, "id = ?", String.valueOf(classroom.get_id())); c = LitePal.findBySQL("select * from " + tableName + " where " + column + " = ?", String.valueOf(classroom.get_id())); assertEquals(0, c.getCount()); c.close(); } private void initGameRoom() { gameRoom = new Classroom(); gameRoom.setName("Game room"); } private void initJude() { jude = new Student(); jude.setName("Jude"); jude.setAge(13); judeCard = new IdCard(); judeCard.setAddress("Jude Street"); judeCard.setNumber("123456"); jude.setIdcard(judeCard); judeCard.setStudent(jude); } private void initRose() { rose = new Student(); rose.setName("Rose"); rose.setAge(15); roseCard = new IdCard(); roseCard.setAddress("Rose Street"); roseCard.setNumber("123457"); roseCard.setStudent(rose); } private void initJohn() { john = new Teacher(); john.setTeacherName("John"); john.setAge(33); john.setTeachYears(13); johnCard = new IdCard(); johnCard.setAddress("John Street"); johnCard.setNumber("123458"); john.setIdCard(johnCard); } private void initMike() { mike = new Teacher(); mike.setTeacherName("Mike"); mike.setAge(36); mike.setTeachYears(16); mikeCard = new IdCard(); mikeCard.setAddress("Mike Street"); mikeCard.setNumber("123459"); mike.setIdCard(mikeCard); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryBasicKotlinTest.kt ================================================ package com.litepaltest.test.crud.query import androidx.test.filters.SmallTest import com.litepaltest.model.Book import com.litepaltest.test.LitePalTestCase import junit.framework.TestCase.* import org.junit.Test import org.litepal.LitePal import org.litepal.extension.* @SmallTest class QueryBasicKotlinTest : LitePalTestCase() { @Test fun testFind() { val isbn: Short = 30013 val book = Book() book.area = 10.5f book.bookName = "Android Second Line" book.isbn = isbn book.level = 'A' book.pages = 450 book.price = 49.99 book.isPublished = false book.save() val b: Book? = LitePal.find(book.id) assertNotNull(b) assertEquals(book.id, b!!.id) assertEquals(10.5f, b.area) assertEquals("Android Second Line", b.bookName) assertEquals(isbn, b.isbn) assertEquals('A', b.level) assertTrue(450 == b.pages) assertEquals(49.99, b.price) assertFalse(b.isPublished) assertTrue(b.isSaved) } @Test fun testFindMul() { val isbn1: Short = 30017 val book1 = Book() book1.area = 1.5f book1.bookName = "Android Second Line" book1.isbn = isbn1 book1.level = 'B' book1.pages = 434 book1.price = 40.99 book1.isPublished = true book1.save() val isbn2: Short = 30014 val book2 = Book() book2.area = 8.8f book2.bookName = "Android Third Line" book2.isbn = isbn2 book2.level = 'C' book2.pages = 411 book2.price = 35.99 book2.isPublished = false book2.save() val bookList = LitePal.findAll(book1.id, book2.id) assertEquals(2, bookList.size) for (book in bookList) { if (book.id == book1.id) { assertEquals(1.5f, book.area) assertEquals("Android Second Line", book.bookName) assertEquals(isbn1, book.isbn) assertEquals('B', book.level) assertTrue(434 == book.pages) assertEquals(40.99, book.price) assertTrue(book.isPublished) assertTrue(book.isSaved) continue } else if (book.id == book2.id) { assertEquals(8.8f, book.area) assertEquals("Android Third Line", book.bookName) assertEquals(isbn2, book.isbn) assertEquals('C', book.level) assertTrue(411 == book.pages) assertEquals(35.99, book.price) assertFalse(book.isPublished) assertTrue(book.isSaved) continue } fail() } } @Test fun testFindAll() { val expectBooks = getBooks(null, null, null, null, null, null, null) val realBooks = LitePal.findAll() assertEquals(expectBooks.size, realBooks.size) for (i in expectBooks.indices) { val expectBook = expectBooks[i] val realBook = realBooks[i] assertEquals(expectBook.id, realBook.id) assertEquals(expectBook.bookName, realBook.bookName) assertEquals(expectBook.pages, realBook.pages) assertEquals(expectBook.price, realBook.price) assertEquals(expectBook.area, realBook.area) assertEquals(expectBook.isbn, realBook.isbn) assertEquals(expectBook.level, realBook.level) assertEquals(expectBook.isPublished, realBook.isPublished) assertTrue(realBook.isSaved) } } @Test fun testFindFirst() { val expectedBooks = getBooks(null, null, null, null, null, null, null) val expectedFirstBook = expectedBooks[0] val realFirstBook = LitePal.findFirst() assertEquals(expectedFirstBook.id, realFirstBook!!.id) assertEquals(expectedFirstBook.bookName, realFirstBook.bookName) assertEquals(expectedFirstBook.pages, realFirstBook.pages) assertEquals(expectedFirstBook.price, realFirstBook.price) assertEquals(expectedFirstBook.area, realFirstBook.area) assertEquals(expectedFirstBook.isbn, realFirstBook.isbn) assertEquals(expectedFirstBook.level, realFirstBook.level) assertEquals(expectedFirstBook.isPublished, realFirstBook.isPublished) assertTrue(realFirstBook.isSaved) } @Test fun testFindLast() { val expectedBooks = getBooks(null, null, null, null, null, null, null) val expectedLastBook = expectedBooks[expectedBooks.size - 1] val realLastBook = LitePal.findLast() assertEquals(expectedLastBook.id, realLastBook!!.id) assertEquals(expectedLastBook.bookName, realLastBook.bookName) assertEquals(expectedLastBook.pages, realLastBook.pages) assertEquals(expectedLastBook.price, realLastBook.price) assertEquals(expectedLastBook.area, realLastBook.area) assertEquals(expectedLastBook.isbn, realLastBook.isbn) assertEquals(expectedLastBook.level, realLastBook.level) assertEquals(expectedLastBook.isPublished, realLastBook.isPublished) assertTrue(realLastBook.isSaved) } @Test fun testIsExist() { val book = Book() book.area = 10.5f book.bookName = "Android Third Line" book.pages = 556 book.price = 49.99 book.isPublished = false book.save() val book2 = Book() book2.area = 10.5f book2.bookName = "Android Fourth Line" book2.pages = 818 book2.price = 59.99 book2.isPublished = false book2.save() assertTrue(LitePal.isExist("bookname = ? and pages = ?", "Android Third Line", "556")) assertFalse(LitePal.isExist("bookname = ? and pages = ?", "Android Third Lines", "556")) assertTrue(LitePal.isExist("bookname = ? and pages = ?", "Android Fourth Line", "818")) assertFalse(LitePal.isExist("bookname = ? and pages = ?", "Android Fourth Line", "813")) } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryBasicTest.java ================================================ package com.litepaltest.test.crud.query; import androidx.test.filters.SmallTest; import com.litepaltest.model.Book; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import org.litepal.LitePal; import java.util.List; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; @SmallTest public class QueryBasicTest extends LitePalTestCase { @Test public void testFind() { short isbn = 30013; Book book = new Book(); book.setArea(10.5f); book.setBookName("Android First Line"); book.setIsbn(isbn); book.setLevel('A'); book.setPages(450); book.setPrice(49.99); book.setPublished(false); book.save(); Book b = LitePal.find(Book.class, book.getId()); assertEquals(book.getId(), b.getId()); assertEquals(10.5f, b.getArea()); assertEquals("Android First Line", b.getBookName()); assertEquals(isbn, b.getIsbn()); assertEquals('A', b.getLevel()); assertTrue(450 == b.getPages()); assertEquals(49.99, b.getPrice()); assertFalse(b.isPublished()); assertTrue(b.isSaved()); } @Test public void testFindMul() { short isbn1 = 30017; Book book1 = new Book(); book1.setArea(1.5f); book1.setBookName("Android Second Line"); book1.setIsbn(isbn1); book1.setLevel('B'); book1.setPages(434); book1.setPrice(40.99); book1.setPublished(true); book1.save(); short isbn2 = 30014; Book book2 = new Book(); book2.setArea(8.8f); book2.setBookName("Android Third Line"); book2.setIsbn(isbn2); book2.setLevel('C'); book2.setPages(411); book2.setPrice(35.99); book2.setPublished(false); book2.save(); List bookList = LitePal.findAll(Book.class, book1.getId(), book2.getId()); assertEquals(2, bookList.size()); for (Book book : bookList) { if (book.getId() == book1.getId()) { assertEquals(1.5f, book.getArea()); assertEquals("Android Second Line", book.getBookName()); assertEquals(isbn1, book.getIsbn()); assertEquals('B', book.getLevel()); assertTrue(434 == book.getPages()); assertEquals(40.99, book.getPrice()); assertTrue(book.isPublished()); assertTrue(book.isSaved()); continue; } else if (book.getId() == book2.getId()) { assertEquals(8.8f, book.getArea()); assertEquals("Android Third Line", book.getBookName()); assertEquals(isbn2, book.getIsbn()); assertEquals('C', book.getLevel()); assertTrue(411 == book.getPages()); assertEquals(35.99, book.getPrice()); assertFalse(book.isPublished()); assertTrue(book.isSaved()); continue; } fail(); } } @Test public void testFindAll() { List expectBooks = getBooks(null, null, null, null, null, null, null); List realBooks = LitePal.findAll(Book.class); assertEquals(expectBooks.size(), realBooks.size()); for (int i = 0; i < expectBooks.size(); i++) { Book expectBook = expectBooks.get(i); Book realBook = realBooks.get(i); assertEquals(expectBook.getId(), realBook.getId()); assertEquals(expectBook.getBookName(), realBook.getBookName()); assertEquals(expectBook.getPages(), realBook.getPages()); assertEquals(expectBook.getPrice(), realBook.getPrice()); assertEquals(expectBook.getArea(), realBook.getArea()); assertEquals(expectBook.getIsbn(), realBook.getIsbn()); assertEquals(expectBook.getLevel(), realBook.getLevel()); assertEquals(expectBook.isPublished(), realBook.isPublished()); assertTrue(realBook.isSaved()); } } @Test public void testFindFirst() { List expectedBooks = getBooks(null, null, null, null, null, null, null); Book expectedFirstBook = expectedBooks.get(0); Book realFirstBook = LitePal.findFirst(Book.class); assertEquals(expectedFirstBook.getId(), realFirstBook.getId()); assertEquals(expectedFirstBook.getBookName(), realFirstBook.getBookName()); assertEquals(expectedFirstBook.getPages(), realFirstBook.getPages()); assertEquals(expectedFirstBook.getPrice(), realFirstBook.getPrice()); assertEquals(expectedFirstBook.getArea(), realFirstBook.getArea()); assertEquals(expectedFirstBook.getIsbn(), realFirstBook.getIsbn()); assertEquals(expectedFirstBook.getLevel(), realFirstBook.getLevel()); assertEquals(expectedFirstBook.isPublished(), realFirstBook.isPublished()); assertTrue(realFirstBook.isSaved()); } @Test public void testFindLast() { List expectedBooks = getBooks(null, null, null, null, null, null, null); Book expectedLastBook = expectedBooks.get(expectedBooks.size() - 1); Book realLastBook = LitePal.findLast(Book.class); assertEquals(expectedLastBook.getId(), realLastBook.getId()); assertEquals(expectedLastBook.getBookName(), realLastBook.getBookName()); assertEquals(expectedLastBook.getPages(), realLastBook.getPages()); assertEquals(expectedLastBook.getPrice(), realLastBook.getPrice()); assertEquals(expectedLastBook.getArea(), realLastBook.getArea()); assertEquals(expectedLastBook.getIsbn(), realLastBook.getIsbn()); assertEquals(expectedLastBook.getLevel(), realLastBook.getLevel()); assertEquals(expectedLastBook.isPublished(), realLastBook.isPublished()); assertTrue(realLastBook.isSaved()); } @Test public void testIsExist() { Book book = new Book(); book.setArea(10.5f); book.setBookName("Android Third Line"); book.setPages(556); book.setPrice(49.99); book.setPublished(false); book.save(); Book book2 = new Book(); book2.setArea(10.5f); book2.setBookName("Android Fourth Line"); book2.setPages(818); book2.setPrice(59.99); book2.setPublished(false); book2.save(); assertTrue(LitePal.isExist(Book.class, "bookname = ? and pages = ?", "Android Third Line", "556")); assertFalse(LitePal.isExist(Book.class, "bookname = ? and pages = ?", "Android Third Lines", "556")); assertTrue(LitePal.isExist(Book.class, "bookname = ? and pages = ?", "Android Fourth Line", "818")); assertFalse(LitePal.isExist(Book.class, "bookname = ? and pages = ?", "Android Fourth Line", "813")); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryBySQLTest.java ================================================ package com.litepaltest.test.crud.query; import android.database.Cursor; import androidx.test.filters.SmallTest; import com.litepaltest.model.Book; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import org.litepal.exceptions.DataSupportException; import org.litepal.util.DBUtility; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; @SmallTest public class QueryBySQLTest { private Book book; private String bookTable; @Before public void setUp() { bookTable = DBUtility.getTableNameByClassName(Book.class.getName()); book = new Book(); book.setBookName("数据库"); book.setPages(300); book.save(); } @Test public void testQueryBySQL() { Cursor cursor = LitePal.findBySQL("select * from " + bookTable); assertTrue(cursor.getCount() > 0); cursor.close(); } @Test public void testQueryBySQLWithPlaceHolder() { Cursor cursor = LitePal.findBySQL( "select * from " + bookTable + " where id=? and bookname=? and pages=?", String.valueOf(book.getId()), "数据库", "300"); assertEquals(1, cursor.getCount()); cursor.moveToFirst(); String bookName = cursor.getString(cursor.getColumnIndexOrThrow("bookname")); int pages = cursor.getInt(cursor.getColumnIndexOrThrow("pages")); assertEquals(bookName, "数据库"); assertEquals(pages, 300); cursor.close(); } @Test public void testQueryBySQLWithWrongParams() { try { LitePal.findBySQL("select * from " + bookTable + " where id=? and bookname=? and pages=?", String.valueOf(book.getId()), "数据库"); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } Cursor cursor = LitePal.findBySQL(); assertNull(cursor); cursor = LitePal.findBySQL(); assertNull(cursor); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryClusterKotlinTest.kt ================================================ package com.litepaltest.test.crud.query import androidx.test.filters.SmallTest import com.litepaltest.model.Book import com.litepaltest.test.LitePalTestCase import junit.framework.TestCase.* import org.junit.Test import org.litepal.LitePal import org.litepal.extension.* @SmallTest class QueryClusterKotlinTest : LitePalTestCase() { @Test fun testSelect() { val expectedBooks = getBooks(null, null, null, null, null, null, null) val books = LitePal.select("bookname", "price").find() assertEquals(expectedBooks.size, books.size) val firstBook = LitePal.select("bookname", "price").findFirst() val lastBook = LitePal.select("bookname", "price").findLast() assertNotNull(firstBook) for (i in books.indices) { val book = books[i] assertTrue(book.isSaved) assertEquals(expectedBooks[i].bookName, book.bookName) assertNull(book.pages) assertEquals(false, book.isPublished) assertEquals(0f, book.area) assertEquals(expectedBooks[i].price, book.price) assertEquals(0, book.isbn.toInt()) assertEquals(0, book.level.toInt()) assertEquals(expectedBooks[i].id, book.id) if (i == 0) { assertEquals(firstBook!!.isSaved, book.isSaved) assertEquals(firstBook.bookName, book.bookName) assertNull(firstBook.pages) assertEquals(firstBook.isPublished, book.isPublished) assertEquals(firstBook.price, book.price) assertEquals(firstBook.area, book.area) assertEquals(firstBook.isbn, book.isbn) assertEquals(firstBook.level, book.level) assertEquals(firstBook.id, book.id) } if (i == books.size - 1) { assertEquals(lastBook!!.isSaved, book.isSaved) assertEquals(lastBook.bookName, book.bookName) assertNull(lastBook.pages) assertEquals(lastBook.isPublished, book.isPublished) assertEquals(lastBook.price, book.price) assertEquals(lastBook.area, book.area) assertEquals(lastBook.isbn, book.isbn) assertEquals(lastBook.level, book.level) assertEquals(lastBook.id, book.id) } } } @Test fun testWhere() { val books = LitePal.where("bookname = ?", "Android First Line").find() val firstBook = LitePal.where("bookname = ?", "Android First Line").findFirst() val lastBook = LitePal.where("bookname = ?", "Android First Line").findLast() for (i in books.indices) { val book = books[i] assertTrue(book.isSaved) assertEquals("Android First Line", book.bookName) assertTrue(450 == book.pages) assertEquals(49.99, book.price) assertEquals(false, book.isPublished) assertEquals('A', book.level) assertEquals(10.5f, book.area) if (i == 0) { assertEquals(firstBook!!.isSaved, book.isSaved) assertEquals(firstBook.bookName, book.bookName) assertEquals(firstBook.pages, book.pages) assertEquals(firstBook.isPublished, book.isPublished) assertEquals(firstBook.price, book.price) assertEquals(firstBook.area, book.area) assertEquals(firstBook.isbn, book.isbn) assertEquals(firstBook.level, book.level) assertEquals(firstBook.id, book.id) } if (i == books.size - 1) { assertEquals(lastBook!!.isSaved, book.isSaved) assertEquals(lastBook.bookName, book.bookName) assertEquals(lastBook.pages, book.pages) assertEquals(lastBook.isPublished, book.isPublished) assertEquals(lastBook.price, book.price) assertEquals(lastBook.area, book.area) assertEquals(lastBook.isbn, book.isbn) assertEquals(lastBook.level, book.level) assertEquals(lastBook.id, book.id) } } val expectedBooks = getBooks(null, "bookname like ?", arrayOf("Android%Line"), null, null, null, null) val realBooks = LitePal.where("bookname like ?", "Android%Line").find() assertEquals(expectedBooks.size, realBooks.size) } @Test fun testOrder() { val books = LitePal.order("ID").find() val firstBook = LitePal.order("ID").findFirst() val lastBook = LitePal.order("ID").findLast() var preBook: Book? = null for (i in books.indices) { val book = books[i] assertTrue(book.isSaved) if (preBook != null) { assertTrue(book.id > preBook.id) } preBook = book if (i == 0) { assertEquals(firstBook!!.isSaved, book.isSaved) assertEquals(firstBook.bookName, book.bookName) assertEquals(firstBook.pages, book.pages) assertEquals(firstBook.isPublished, book.isPublished) assertEquals(firstBook.price, book.price) assertEquals(firstBook.area, book.area) assertEquals(firstBook.isbn, book.isbn) assertEquals(firstBook.level, book.level) assertEquals(firstBook.id, book.id) } if (i == books.size - 1) { assertEquals(lastBook!!.isSaved, book.isSaved) assertEquals(lastBook.bookName, book.bookName) assertEquals(lastBook.pages, book.pages) assertEquals(lastBook.isPublished, book.isPublished) assertEquals(lastBook.price, book.price) assertEquals(lastBook.area, book.area) assertEquals(lastBook.isbn, book.isbn) assertEquals(lastBook.level, book.level) assertEquals(lastBook.id, book.id) } } val inverseBooks = LitePal.order("ID desc").find() val inverseFirstBook = LitePal.order("ID desc").findFirst() val inverseLastBook = LitePal.order("ID desc").findLast() var inversePreBook: Book? = null for (i in inverseBooks.indices) { val book = inverseBooks[i] assertTrue(book.isSaved) if (inversePreBook != null) { assertTrue(book.id < inversePreBook.id) } inversePreBook = book if (i == 0) { assertEquals(inverseFirstBook!!.isSaved, book.isSaved) assertEquals(inverseFirstBook.bookName, book.bookName) assertEquals(inverseFirstBook.pages, book.pages) assertEquals(inverseFirstBook.isPublished, book.isPublished) assertEquals(inverseFirstBook.price, book.price) assertEquals(inverseFirstBook.area, book.area) assertEquals(inverseFirstBook.isbn, book.isbn) assertEquals(inverseFirstBook.level, book.level) assertEquals(inverseFirstBook.id, book.id) } if (i == books.size - 1) { assertEquals(inverseLastBook!!.isSaved, book.isSaved) assertEquals(inverseLastBook.bookName, book.bookName) assertEquals(inverseLastBook.pages, book.pages) assertEquals(inverseLastBook.isPublished, book.isPublished) assertEquals(inverseLastBook.price, book.price) assertEquals(inverseLastBook.area, book.area) assertEquals(inverseLastBook.isbn, book.isbn) assertEquals(inverseLastBook.level, book.level) assertEquals(inverseLastBook.id, book.id) } } } @Test fun testLimit() { var bookList = LitePal.limit(1).find() assertEquals(1, bookList.size) var book = bookList[0] assertTrue(book.isSaved) val firstBook = LitePal.findFirst() assertTrue(firstBook!!.isSaved) assertEquals(firstBook.bookName, book.bookName) assertEquals(firstBook.pages, book.pages) assertEquals(firstBook.isPublished, book.isPublished) assertEquals(firstBook.area, book.area) assertEquals(firstBook.price, book.price) assertEquals(firstBook.isbn, book.isbn) assertEquals(firstBook.level, book.level) assertEquals(firstBook.id, book.id) bookList = LitePal.order("id desc").limit(1).find() assertEquals(1, bookList.size) book = bookList[0] assertTrue(book.isSaved) val lastBook = LitePal.findLast(Book::class.java) assertTrue(lastBook!!.isSaved) assertEquals(lastBook.bookName, book.bookName) assertEquals(lastBook.pages, book.pages) assertEquals(lastBook.isPublished, book.isPublished) assertEquals(lastBook.area, book.area) assertEquals(lastBook.price, book.price) assertEquals(lastBook.isbn, book.isbn) assertEquals(lastBook.level, book.level) assertEquals(lastBook.id, book.id) } @Test fun testOffset() { val list = LitePal.offset(1).find() assertEquals(0, list.size) val bookList = LitePal.limit(1).offset(1).find() assertEquals(1, bookList.size) val book = bookList[0] assertTrue(book.isSaved) val expectedBooks = getBooks(null, null, null, null, null, null, null) val expectedBook = expectedBooks[1] assertEquals(expectedBook.bookName, book.bookName) assertEquals(expectedBook.pages, book.pages) assertEquals(expectedBook.isPublished, book.isPublished) assertEquals(expectedBook.area, book.area) assertEquals(expectedBook.price, book.price) assertEquals(expectedBook.isbn, book.isbn) assertEquals(expectedBook.level, book.level) assertEquals(expectedBook.id, book.id) } @Test fun testCluster() { val ids = LongArray(3) for (i in 0..2) { val book = Book() book.pages = 5555 book.isPublished = true book.price = 40.99 book.save() ids[i] = book.id } val books = LitePal .select("pages", "isPublished") .where("id=? or id=? or id=?", ids[0].toString(), ids[1].toString(), ids[2].toString()).order("id").limit(2).offset(1).find() val firstBook = LitePal .select("pages", "isPublished") .where("id=? or id=? or id=?", ids[0].toString(), ids[1].toString(), ids[2].toString()).order("id").limit(2).offset(1).findFirst() val lastBook = LitePal .select("pages", "isPublished") .where("id=? or id=? or id=?", ids[0].toString(), ids[1].toString(), ids[2].toString()).order("id").limit(2).offset(1).findLast() assertEquals(2, books.size) assertTrue(books[0].id < books[1].id) for (i in 0..1) { val b = books[i] assertEquals(ids[i + 1], b.id) assertTrue(b.isSaved) assertNull(b.bookName) assertTrue(5555 == b.pages) assertEquals(true, b.isPublished) assertEquals(0f, b.area) assertEquals(0.0, b.price) assertEquals(0, b.isbn.toInt()) assertEquals(0, b.level.toInt()) if (i == 0) { assertEquals(firstBook!!.isSaved, b.isSaved) assertEquals(firstBook.bookName, b.bookName) assertEquals(firstBook.pages, b.pages) assertEquals(firstBook.isPublished, b.isPublished) assertEquals(firstBook.price, b.price) assertEquals(firstBook.area, b.area) assertEquals(firstBook.isbn, b.isbn) assertEquals(firstBook.level, b.level) assertEquals(firstBook.id, b.id) } if (i == books.size - 1) { assertEquals(lastBook!!.isSaved, b.isSaved) assertEquals(lastBook.bookName, b.bookName) assertEquals(lastBook.pages, b.pages) assertEquals(lastBook.isPublished, b.isPublished) assertEquals(lastBook.price, b.price) assertEquals(lastBook.area, b.area) assertEquals(lastBook.isbn, b.isbn) assertEquals(lastBook.level, b.level) assertEquals(lastBook.id, b.id) } } } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryClusterTest.java ================================================ package com.litepaltest.test.crud.query; import androidx.test.filters.SmallTest; import java.util.List; import org.junit.Test; import org.litepal.LitePal; import com.litepaltest.model.Book; import com.litepaltest.test.LitePalTestCase; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; @SmallTest public class QueryClusterTest extends LitePalTestCase { @Test public void testSelect() { List expectedBooks = getBooks(null, null, null, null, null, null, null); List books = LitePal.select("bookname", "price").find(Book.class); assertEquals(expectedBooks.size(), books.size()); Book firstBook = LitePal.select("bookname", "price").findFirst(Book.class); Book lastBook = LitePal.select("bookname", "price").findLast(Book.class); assertNotNull(firstBook); for (int i = 0; i < books.size(); i++) { Book book = books.get(i); assertTrue(book.isSaved()); assertEquals(expectedBooks.get(i).getBookName(), book.getBookName()); assertNull(book.getPages()); assertFalse(book.isPublished()); assertEquals(0f, book.getArea()); assertEquals(expectedBooks.get(i).getPrice(), book.getPrice()); assertEquals(0, book.getIsbn()); assertEquals(0, book.getLevel()); assertEquals(expectedBooks.get(i).getId(), book.getId()); if (i == 0) { assertEquals(firstBook.isSaved(), book.isSaved()); assertEquals(firstBook.getBookName(), book.getBookName()); assertEquals(firstBook.getPages(), book.getPages()); assertEquals(firstBook.isPublished(), book.isPublished()); assertEquals(firstBook.getPrice(), book.getPrice()); assertEquals(firstBook.getArea(), book.getArea()); assertEquals(firstBook.getIsbn(), book.getIsbn()); assertEquals(firstBook.getLevel(), book.getLevel()); assertEquals(firstBook.getId(), book.getId()); } if (i == books.size() - 1) { assertEquals(lastBook.isSaved(), book.isSaved()); assertEquals(lastBook.getBookName(), book.getBookName()); assertEquals(lastBook.getPages(), book.getPages()); assertEquals(lastBook.isPublished(), book.isPublished()); assertEquals(lastBook.getPrice(), book.getPrice()); assertEquals(lastBook.getArea(), book.getArea()); assertEquals(lastBook.getIsbn(), book.getIsbn()); assertEquals(lastBook.getLevel(), book.getLevel()); assertEquals(lastBook.getId(), book.getId()); } } } @Test public void testWhere() { List books = LitePal.where("bookname = ?", "Android First Line").find(Book.class); Book firstBook = LitePal.where("bookname = ?", "Android First Line").findFirst(Book.class); Book lastBook = LitePal.where("bookname = ?", "Android First Line").findLast(Book.class); for (int i = 0; i < books.size(); i++) { Book book = books.get(i); assertTrue(book.isSaved()); assertEquals("Android First Line", book.getBookName()); assertEquals(450, (int) book.getPages()); assertEquals(49.99, book.getPrice()); assertFalse(book.isPublished()); assertEquals('A', book.getLevel()); assertEquals(10.5f, book.getArea()); if (i == 0) { assertEquals(firstBook.isSaved(), book.isSaved()); assertEquals(firstBook.getBookName(), book.getBookName()); assertEquals(firstBook.getPages(), book.getPages()); assertEquals(firstBook.isPublished(), book.isPublished()); assertEquals(firstBook.getPrice(), book.getPrice()); assertEquals(firstBook.getArea(), book.getArea()); assertEquals(firstBook.getIsbn(), book.getIsbn()); assertEquals(firstBook.getLevel(), book.getLevel()); assertEquals(firstBook.getId(), book.getId()); } if (i == books.size() - 1) { assertEquals(lastBook.isSaved(), book.isSaved()); assertEquals(lastBook.getBookName(), book.getBookName()); assertEquals(lastBook.getPages(), book.getPages()); assertEquals(lastBook.isPublished(), book.isPublished()); assertEquals(lastBook.getPrice(), book.getPrice()); assertEquals(lastBook.getArea(), book.getArea()); assertEquals(lastBook.getIsbn(), book.getIsbn()); assertEquals(lastBook.getLevel(), book.getLevel()); assertEquals(lastBook.getId(), book.getId()); } } List expectedBooks = getBooks(null, "bookname like ?", new String[] { "Android%Line" }, null, null, null, null); List realBooks = LitePal.where("bookname like ?", "Android%Line") .find(Book.class); assertEquals(expectedBooks.size(), realBooks.size()); } @Test public void testOrder() { List books = LitePal.order("ID").find(Book.class); Book firstBook = LitePal.order("ID").findFirst(Book.class); Book lastBook = LitePal.order("ID").findLast(Book.class); Book preBook = null; for (int i = 0; i < books.size(); i++) { Book book = books.get(i); assertTrue(book.isSaved()); if (preBook != null) { assertTrue(book.getId() > preBook.getId()); } preBook = book; if (i == 0) { assertEquals(firstBook.isSaved(), book.isSaved()); assertEquals(firstBook.getBookName(), book.getBookName()); assertEquals(firstBook.getPages(), book.getPages()); assertEquals(firstBook.isPublished(), book.isPublished()); assertEquals(firstBook.getPrice(), book.getPrice()); assertEquals(firstBook.getArea(), book.getArea()); assertEquals(firstBook.getIsbn(), book.getIsbn()); assertEquals(firstBook.getLevel(), book.getLevel()); assertEquals(firstBook.getId(), book.getId()); } if (i == books.size() - 1) { assertEquals(lastBook.isSaved(), book.isSaved()); assertEquals(lastBook.getBookName(), book.getBookName()); assertEquals(lastBook.getPages(), book.getPages()); assertEquals(lastBook.isPublished(), book.isPublished()); assertEquals(lastBook.getPrice(), book.getPrice()); assertEquals(lastBook.getArea(), book.getArea()); assertEquals(lastBook.getIsbn(), book.getIsbn()); assertEquals(lastBook.getLevel(), book.getLevel()); assertEquals(lastBook.getId(), book.getId()); } } List inverseBooks = LitePal.order("ID desc").find(Book.class); Book inverseFirstBook = LitePal.order("ID desc").findFirst(Book.class); Book inverseLastBook = LitePal.order("ID desc").findLast(Book.class); Book inversePreBook = null; for (int i = 0; i < inverseBooks.size(); i++) { Book book = inverseBooks.get(i); assertTrue(book.isSaved()); if (inversePreBook != null) { assertTrue(book.getId() < inversePreBook.getId()); } inversePreBook = book; if (i == 0) { assertEquals(inverseFirstBook.isSaved(), book.isSaved()); assertEquals(inverseFirstBook.getBookName(), book.getBookName()); assertEquals(inverseFirstBook.getPages(), book.getPages()); assertEquals(inverseFirstBook.isPublished(), book.isPublished()); assertEquals(inverseFirstBook.getPrice(), book.getPrice()); assertEquals(inverseFirstBook.getArea(), book.getArea()); assertEquals(inverseFirstBook.getIsbn(), book.getIsbn()); assertEquals(inverseFirstBook.getLevel(), book.getLevel()); assertEquals(inverseFirstBook.getId(), book.getId()); } if (i == books.size() - 1) { assertEquals(inverseLastBook.isSaved(), book.isSaved()); assertEquals(inverseLastBook.getBookName(), book.getBookName()); assertEquals(inverseLastBook.getPages(), book.getPages()); assertEquals(inverseLastBook.isPublished(), book.isPublished()); assertEquals(inverseLastBook.getPrice(), book.getPrice()); assertEquals(inverseLastBook.getArea(), book.getArea()); assertEquals(inverseLastBook.getIsbn(), book.getIsbn()); assertEquals(inverseLastBook.getLevel(), book.getLevel()); assertEquals(inverseLastBook.getId(), book.getId()); } } } @Test public void testLimit() { List bookList = LitePal.limit(1).find(Book.class); assertEquals(1, bookList.size()); Book book = bookList.get(0); assertTrue(book.isSaved()); Book firstBook = LitePal.findFirst(Book.class); assertTrue(firstBook.isSaved()); assertEquals(firstBook.getBookName(), book.getBookName()); assertEquals(firstBook.getPages(), book.getPages()); assertEquals(firstBook.isPublished(), book.isPublished()); assertEquals(firstBook.getArea(), book.getArea()); assertEquals(firstBook.getPrice(), book.getPrice()); assertEquals(firstBook.getIsbn(), book.getIsbn()); assertEquals(firstBook.getLevel(), book.getLevel()); assertEquals(firstBook.getId(), book.getId()); bookList = LitePal.order("id desc").limit(1).find(Book.class); assertEquals(1, bookList.size()); book = bookList.get(0); assertTrue(book.isSaved()); Book lastBook = LitePal.findLast(Book.class); assertTrue(lastBook.isSaved()); assertEquals(lastBook.getBookName(), book.getBookName()); assertEquals(lastBook.getPages(), book.getPages()); assertEquals(lastBook.isPublished(), book.isPublished()); assertEquals(lastBook.getArea(), book.getArea()); assertEquals(lastBook.getPrice(), book.getPrice()); assertEquals(lastBook.getIsbn(), book.getIsbn()); assertEquals(lastBook.getLevel(), book.getLevel()); assertEquals(lastBook.getId(), book.getId()); } @Test public void testOffset() { List list = LitePal.offset(1).find(Book.class); assertEquals(0, list.size()); List bookList = LitePal.limit(1).offset(1).find(Book.class); assertEquals(1, bookList.size()); Book book = bookList.get(0); assertTrue(book.isSaved()); List expectedBooks = getBooks(null, null, null, null, null, null, null); Book expectedBook = expectedBooks.get(1); assertEquals(expectedBook.getBookName(), book.getBookName()); assertEquals(expectedBook.getPages(), book.getPages()); assertEquals(expectedBook.isPublished(), book.isPublished()); assertEquals(expectedBook.getArea(), book.getArea()); assertEquals(expectedBook.getPrice(), book.getPrice()); assertEquals(expectedBook.getIsbn(), book.getIsbn()); assertEquals(expectedBook.getLevel(), book.getLevel()); assertEquals(expectedBook.getId(), book.getId()); } @Test public void testCluster() { long[] ids = new long[3]; for (int i = 0; i < 3; i++) { Book book = new Book(); book.setPages(5555); book.setPublished(true); book.setPrice(40.99); book.save(); ids[i] = book.getId(); } List books = LitePal .select("pages", "isPublished") .where("id=? or id=? or id=?", String.valueOf(ids[0]), String.valueOf(ids[1]), String.valueOf(ids[2])).order("id").limit(2).offset(1).find(Book.class); Book firstBook = LitePal .select("pages", "isPublished") .where("id=? or id=? or id=?", String.valueOf(ids[0]), String.valueOf(ids[1]), String.valueOf(ids[2])).order("id").limit(2).offset(1).findFirst(Book.class); Book lastBook = LitePal .select("pages", "isPublished") .where("id=? or id=? or id=?", String.valueOf(ids[0]), String.valueOf(ids[1]), String.valueOf(ids[2])).order("id").limit(2).offset(1).findLast(Book.class); assertEquals(2, books.size()); assertTrue(books.get(0).getId() < books.get(1).getId()); for (int i = 0; i < 2; i++) { Book b = books.get(i); assertEquals(ids[i + 1], b.getId()); assertTrue(b.isSaved()); assertNull(b.getBookName()); assertEquals(5555, (int) b.getPages()); assertTrue(b.isPublished()); assertEquals(0f, b.getArea()); assertEquals(0.0, b.getPrice()); assertEquals(0, b.getIsbn()); assertEquals(0, b.getLevel()); if (i == 0) { assertEquals(firstBook.isSaved(), b.isSaved()); assertEquals(firstBook.getBookName(), b.getBookName()); assertEquals(firstBook.getPages(), b.getPages()); assertEquals(firstBook.isPublished(), b.isPublished()); assertEquals(firstBook.getPrice(), b.getPrice()); assertEquals(firstBook.getArea(), b.getArea()); assertEquals(firstBook.getIsbn(), b.getIsbn()); assertEquals(firstBook.getLevel(), b.getLevel()); assertEquals(firstBook.getId(), b.getId()); } if (i == books.size() - 1) { assertEquals(lastBook.isSaved(), b.isSaved()); assertEquals(lastBook.getBookName(), b.getBookName()); assertEquals(lastBook.getPages(), b.getPages()); assertEquals(lastBook.isPublished(), b.isPublished()); assertEquals(lastBook.getPrice(), b.getPrice()); assertEquals(lastBook.getArea(), b.getArea()); assertEquals(lastBook.getIsbn(), b.getIsbn()); assertEquals(lastBook.getLevel(), b.getLevel()); assertEquals(lastBook.getId(), b.getId()); } } Book first1 = LitePal.findFirst(Book.class); Book first2 = LitePal.select("id").findFirst(Book.class); assertEquals(first1.getId(), first2.getId()); Book last1 = LitePal.findLast(Book.class); Book last2 = LitePal.select("id").findLast(Book.class); assertEquals(last1.getId(), last2.getId()); List firstTwoBooks = LitePal.where("id=? or id=? or id=?", String.valueOf(ids[0]), String.valueOf(ids[1]), String.valueOf(ids[2])).order("id").limit(2).find(Book.class); firstBook = LitePal.where("id=? or id=? or id=?", String.valueOf(ids[0]), String.valueOf(ids[1]), String.valueOf(ids[2])).order("id").limit(2).findFirst(Book.class); lastBook = LitePal.where("id=? or id=? or id=?", String.valueOf(ids[0]), String.valueOf(ids[1]), String.valueOf(ids[2])).order("id").limit(2).findLast(Book.class); assertEquals(firstTwoBooks.get(0).getId(), firstBook.getId()); assertEquals(firstTwoBooks.get(1).getId(), lastBook.getId()); } @Test public void testBooleanQuery() { Book book1 = new Book(); book1.setBookName("not published"); book1.setPublished(false); Book book2 = new Book(); book2.setBookName("published"); book2.setPublished(true); book1.save(); book2.save(); Book book1DB = LitePal.where("isPublished = 0").findFirst(Book.class); Book book2DB = LitePal.where("isPublished = 1").findFirst(Book.class); assertNotNull(book1DB); assertNotNull(book2DB); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryDateTest.java ================================================ package com.litepaltest.test.crud.query; import androidx.test.filters.SmallTest; import com.litepaltest.model.Student; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import org.litepal.LitePal; import java.util.Calendar; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; @SmallTest public class QueryDateTest extends LitePalTestCase { @Test public void testQueryDate() { Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(1990, 9, 16, 0, 0, 0); Student student1 = new Student(); student1.setName("Student 1"); student1.setBirthday(calendar.getTime()); student1.save(); Student studentFromDB = LitePal.find(Student.class, student1.getId()); assertEquals("Student 1", studentFromDB.getName()); assertEquals(calendar.getTimeInMillis(), studentFromDB.getBirthday().getTime()); } @Test public void testQueryDateBefore1970() { Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(1920, 6, 3, 0, 0, 0); Student student1 = new Student(); student1.setName("Student 2"); student1.setBirthday(calendar.getTime()); student1.save(); Student studentFromDB = LitePal.find(Student.class, student1.getId()); assertEquals("Student 2", studentFromDB.getName()); assertEquals(calendar.getTimeInMillis(), studentFromDB.getBirthday().getTime()); } @Test public void testQueryDateWithDefaultValue() { Student student = new Student(); student.setName("School Student"); assertTrue(student.save()); Student studentFromDB = LitePal.find(Student.class, student.getId()); assertEquals(1589203961859L, studentFromDB.getSchoolDate().getTime()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryEagerKotlinTest.kt ================================================ package com.litepaltest.test.crud.query import androidx.test.filters.SmallTest import com.litepaltest.model.Classroom import com.litepaltest.model.IdCard import com.litepaltest.model.Student import com.litepaltest.model.Teacher import junit.framework.TestCase.* import org.junit.Before import org.junit.Test import org.litepal.LitePal import org.litepal.extension.deleteAll import org.litepal.extension.find import org.litepal.extension.findAll import org.litepal.extension.findLast import java.util.* @SmallTest class QueryEagerKotlinTest { private var classroom: Classroom? = null private var student1: Student? = null private var student2: Student? = null private var student3: Student? = null private var teacher1: Teacher? = null private var teacher2: Teacher? = null private var idcard1: IdCard? = null private var idcard2: IdCard? = null @Before fun setUp() { val calendar = Calendar.getInstance() classroom = Classroom() classroom!!.name = "Classroom 11" idcard1 = IdCard() idcard1!!.number = "320311" idcard2 = IdCard() idcard2!!.number = "320322" calendar.clear() calendar.set(1990, 9, 16, 0, 0, 0) student1 = Student() student1!!.name = "Student 1" student1!!.classroom = classroom student1!!.idcard = idcard1 student1!!.birthday = calendar.time calendar.clear() calendar.set(1989, 7, 7, 0, 0, 0) student2 = Student() student2!!.name = "Student 2" student2!!.classroom = classroom student2!!.birthday = calendar.time student3 = Student() student3!!.name = "Student 3" teacher1 = Teacher() teacher1!!.teacherName = "Teacher 1" teacher1!!.teachYears = 3 teacher1!!.idCard = idcard2 teacher2 = Teacher() teacher2!!.isSex = false teacher2!!.teacherName = "Teacher 2" student1!!.teachers.add(teacher1) student1!!.teachers.add(teacher2) student2!!.teachers.add(teacher2) classroom!!.teachers.add(teacher1) classroom!!.save() student1!!.save() student2!!.save() student3!!.save() idcard1!!.save() idcard2!!.save() teacher1!!.save() teacher2!!.save() } @Test fun testEagerFind() { var s1 = LitePal.find(student1!!.id.toLong(), true) var c: Classroom? = s1!!.classroom val ic = s1.idcard val tList = s1.teachers assertNotNull(c) assertNotNull(ic) assertEquals(classroom!!._id, c!!._id) assertEquals("Classroom 11", c.name) assertEquals(idcard1!!.id, ic.id) assertEquals("320311", ic.number) assertEquals(student1!!.teachers.size, tList.size) val calendar = Calendar.getInstance() calendar.clear() calendar.set(1990, 9, 16, 0, 0, 0) assertEquals(calendar.time.time, s1.birthday.time) for (t in tList) { if (t.id == teacher1!!.id) { assertEquals("Teacher 1", t.teacherName) assertEquals(teacher1!!.teachYears, t.teachYears) assertTrue(t.isSex) continue } if (t.id == teacher2!!.id) { assertEquals("Teacher 2", t.teacherName) assertFalse(t.isSex) continue } fail() } s1 = LitePal.find(student1!!.id.toLong()) c = s1!!.classroom assertNull(c) assertNull(s1.idcard) assertEquals(0, s1.teachers.size) c = LitePal.find(classroom!!._id.toLong(), true) assertEquals(2, c!!.studentCollection.size) assertEquals(1, c.teachers.size) for (s in c.studentCollection) { if (s.id == student1!!.id) { assertEquals("Student 1", s.name) continue } if (s.id == student2!!.id) { assertEquals("Student 2", s.name) calendar.clear() calendar.set(1989, 7, 7, 0, 0, 0) assertEquals(calendar.time.time, s.birthday.time) continue } fail() } val t1 = LitePal.find(teacher2!!.id.toLong(), true) val sList = t1!!.students assertEquals(teacher2!!.students.size, sList.size) for (s in sList) { if (s.id == student1!!.id) { assertEquals("Student 1", s.name) calendar.clear() calendar.set(1990, 9, 16, 0, 0, 0) assertEquals(calendar.time.time, s.birthday.time) continue } if (s.id == student2!!.id) { assertEquals("Student 2", s.name) continue } fail() } val s3 = LitePal.find(student3!!.id.toLong()) assertNull(s3!!.birthday) } private fun resetData() { LitePal.deleteAll() LitePal.deleteAll() LitePal.deleteAll() LitePal.deleteAll() setUp() } @Test fun testEagerFindFirst() { resetData() var s1 = LitePal.findFirst(Student::class.java) assertNull(s1!!.classroom) s1 = LitePal.findFirst(Student::class.java, true) assertNotNull(s1) } @Test fun testEagerFindLast() { resetData() var t1 = LitePal.findLast() assertEquals(0, t1!!.students.size) t1 = LitePal.findLast(true) assertTrue(0 < t1!!.students.size) } @Test fun testEagerFindAll() { resetData() var sList = LitePal.findAll() for (s in sList) { assertNull(s.classroom) assertEquals(0, s.teachers.size) } sList = LitePal.findAll(true) for (s in sList) { if (s.classroom == null) { continue } assertEquals("Classroom 11", s.classroom.name) assertTrue(s.teachers.size > 0) val tList = s.teachers for (t in tList) { if (t.id == teacher1!!.id) { assertEquals("Teacher 1", t.teacherName) assertEquals(teacher1!!.teachYears, t.teachYears) assertTrue(t.isSex) continue } if (t.id == teacher2!!.id) { assertEquals("Teacher 2", t.teacherName) assertFalse(t.isSex) continue } fail() } } } @Test fun testEagerClusterQuery() { resetData() var sList = LitePal.where("id = ?", student1!!.id.toString()).find() assertEquals(1, sList.size) var s = sList[0] assertNull(s.classroom) sList = LitePal.where("id = ?", student1!!.id.toString()).find(true) assertEquals(1, sList.size) s = sList[0] assertNotNull(s.classroom) val c = s.classroom assertEquals("Classroom 11", c.name) } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryEagerTest.java ================================================ package com.litepaltest.test.crud.query; import androidx.test.filters.SmallTest; import com.litepaltest.model.Classroom; import com.litepaltest.model.IdCard; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import java.util.Calendar; import java.util.List; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; @SmallTest public class QueryEagerTest { private Classroom classroom; private Student student1; private Student student2; private Student student3; private Teacher teacher1; private Teacher teacher2; private IdCard idcard1; @Before public void setUp() { Calendar calendar = Calendar.getInstance(); classroom = new Classroom(); classroom.setName("Classroom 11"); idcard1 = new IdCard(); idcard1.setNumber("320311"); IdCard idcard2 = new IdCard(); idcard2.setNumber("320322"); calendar.clear(); calendar.set(1990, 9, 16, 0, 0, 0); student1 = new Student(); student1.setName("Student 1"); student1.setClassroom(classroom); student1.setIdcard(idcard1); student1.setBirthday(calendar.getTime()); calendar.clear(); calendar.set(1989, 7, 7, 0, 0, 0); student2 = new Student(); student2.setName("Student 2"); student2.setClassroom(classroom); student2.setBirthday(calendar.getTime()); student3 = new Student(); student3.setName("Student 3"); teacher1 = new Teacher(); teacher1.setTeacherName("Teacher 1"); teacher1.setTeachYears(3); teacher1.setIdCard(idcard2); teacher2 = new Teacher(); teacher2.setSex(false); teacher2.setTeacherName("Teacher 2"); student1.getTeachers().add(teacher1); student1.getTeachers().add(teacher2); student2.getTeachers().add(teacher2); classroom.getTeachers().add(teacher1); classroom.save(); student1.save(); student2.save(); student3.save(); idcard1.save(); idcard2.save(); teacher1.save(); teacher2.save(); } @Test public void testEagerFind() { Student s1 = LitePal.find(Student.class, student1.getId(), true); Classroom c = s1.getClassroom(); IdCard ic = s1.getIdcard(); List tList = s1.getTeachers(); assertNotNull(c); assertNotNull(ic); assertEquals(classroom.get_id(), c.get_id()); assertEquals("Classroom 11", c.getName()); assertEquals(idcard1.getId(), ic.getId()); assertEquals("320311", ic.getNumber()); assertEquals(student1.getTeachers().size(), tList.size()); Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(1990, 9, 16, 0, 0, 0); assertEquals(calendar.getTime().getTime(), s1.getBirthday().getTime()); for (Teacher t : tList) { if (t.getId() == teacher1.getId()) { assertEquals("Teacher 1", t.getTeacherName()); assertEquals(teacher1.getTeachYears(), t.getTeachYears()); assertTrue(t.isSex()); continue; } if (t.getId() == teacher2.getId()) { assertEquals("Teacher 2", t.getTeacherName()); assertFalse(t.isSex()); continue; } fail(); } s1 = LitePal.find(Student.class, student1.getId()); c = s1.getClassroom(); assertNull(c); assertNull(s1.getIdcard()); assertEquals(0, s1.getTeachers().size()); c = LitePal.find(Classroom.class, classroom.get_id(), true); assertEquals(2, c.getStudentCollection().size()); assertEquals(1, c.getTeachers().size()); for (Student s : c.getStudentCollection()) { if (s.getId() == student1.getId()) { assertEquals("Student 1", s.getName()); continue; } if (s.getId() == student2.getId()) { assertEquals("Student 2", s.getName()); calendar.clear(); calendar.set(1989, 7, 7, 0, 0, 0); assertEquals(calendar.getTime().getTime(), s.getBirthday().getTime()); continue; } fail(); } Teacher t1 = LitePal.find(Teacher.class, teacher2.getId(), true); List sList = t1.getStudents(); assertEquals(teacher2.getStudents().size(), sList.size()); for (Student s : sList) { if (s.getId() == student1.getId()) { assertEquals("Student 1", s.getName()); calendar.clear(); calendar.set(1990, 9, 16, 0, 0, 0); assertEquals(calendar.getTime().getTime(), s.getBirthday().getTime()); continue; } if (s.getId() == student2.getId()) { assertEquals("Student 2", s.getName()); continue; } fail(); } Student s3 = LitePal.find(Student.class, student3.getId()); assertNull(s3.getBirthday()); } public void resetData() { LitePal.deleteAll(Student.class); LitePal.deleteAll(Classroom.class); LitePal.deleteAll(Teacher.class); LitePal.deleteAll(IdCard.class); setUp(); } @Test public void testEagerFindFirst() { resetData(); Student s1 = LitePal.findFirst(Student.class); assertNull(s1.getClassroom()); s1 = LitePal.findFirst(Student.class, true); assertNotNull(s1); } @Test public void testEagerFindLast() { resetData(); Teacher t1 = LitePal.findLast(Teacher.class); assertEquals(0, t1.getStudents().size()); t1 = LitePal.findLast(Teacher.class, true); assertTrue(0 < t1.getStudents().size()); } @Test public void testEagerFindAll() { resetData(); List sList = LitePal.findAll(Student.class); for (Student s : sList) { assertNull(s.getClassroom()); assertEquals(0, s.getTeachers().size()); } sList = LitePal.findAll(Student.class, true); for (Student s : sList) { if (s.getClassroom() == null) { continue; } assertEquals("Classroom 11", s.getClassroom().getName()); assertTrue(s.getTeachers().size() > 0); List tList = s.getTeachers(); for (Teacher t : tList) { if (t.getId() == teacher1.getId()) { assertEquals("Teacher 1", t.getTeacherName()); assertEquals(teacher1.getTeachYears(), t.getTeachYears()); assertTrue(t.isSex()); continue; } if (t.getId() == teacher2.getId()) { assertEquals("Teacher 2", t.getTeacherName()); assertFalse(t.isSex()); continue; } fail(); } } } @Test public void testEagerClusterQuery() { resetData(); List sList = LitePal.where("id = ?", String.valueOf(student1.getId())).find( Student.class); assertEquals(1, sList.size()); Student s = sList.get(0); assertNull(s.getClassroom()); sList = LitePal.where("id = ?", String.valueOf(student1.getId())).find(Student.class, true); assertEquals(1, sList.size()); s = sList.get(0); assertNotNull(s.getClassroom()); Classroom c = s.getClassroom(); assertEquals("Classroom 11", c.getName()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryMathKotlinTest.kt ================================================ package com.litepaltest.test.crud.query import androidx.test.filters.SmallTest import com.litepaltest.model.Student import junit.framework.TestCase.assertEquals import junit.framework.TestCase.fail import org.junit.Before import org.junit.Test import org.litepal.LitePal import org.litepal.extension.* import org.litepal.util.DBUtility @SmallTest class QueryMathKotlinTest { private var studentTable: String? = null @Before fun setUp() { studentTable = DBUtility.getTableNameByClassName(Student::class.java.name) } @Test fun testCount() { var result = LitePal.count() var realResult = -100 var cursor = LitePal.findBySQL("select count(1) from " + studentTable!!) if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) result = LitePal.where("id > ?", "99").count(studentTable) cursor = LitePal.findBySQL("select count(1) from $studentTable where id > ?", "99") if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) try { LitePal.count("nosuchtable") fail() } catch (e: Exception) { } } @Test fun testAverage() { var result = LitePal.average("age") var realResult = -100.0 var cursor = LitePal.findBySQL("select avg(age) from " + studentTable!!) if (cursor!!.moveToFirst()) { realResult = cursor.getDouble(0) } cursor.close() assertEquals(realResult, result) result = LitePal.where("id > ?", "99").average(studentTable, "age") cursor = LitePal.findBySQL("select avg(age) from $studentTable where id > ?", "99") if (cursor!!.moveToFirst()) { realResult = cursor.getDouble(0) } cursor.close() assertEquals(realResult, result) try { LitePal.average("nosuchcolumn") fail() } catch (e: Exception) { e.printStackTrace() } } @Test fun testMax() { var result = LitePal.max("age") var realResult = -100 var cursor = LitePal.findBySQL("select max(age) from " + studentTable!!) if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) result = LitePal.where("age < ?", "20").max(studentTable!!, "age") cursor = LitePal.findBySQL("select max(age) from $studentTable where age < ?", "20") if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) } @Test fun testMin() { var result = LitePal.min("age") var realResult = -100 var cursor = LitePal.findBySQL("select min(age) from " + studentTable!!) if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) result = LitePal.where("age > ?", "10").min(studentTable!!, "age") cursor = LitePal.findBySQL("select min(age) from $studentTable where age > ?", "10") if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) } @Test fun testSum() { var result = LitePal.sum("age") var realResult = -100 var cursor = LitePal.findBySQL("select sum(age) from " + studentTable!!) if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) result = LitePal.where("age > ?", "15").sum(studentTable!!, "age") cursor = LitePal.findBySQL("select sum(age) from $studentTable where age > ?", "15") if (cursor!!.moveToFirst()) { realResult = cursor.getInt(0) } cursor.close() assertEquals(realResult, result) } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/query/QueryMathTest.java ================================================ package com.litepaltest.test.crud.query; import android.database.Cursor; import androidx.test.filters.SmallTest; import com.litepaltest.model.Student; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import org.litepal.util.DBUtility; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.fail; @SmallTest public class QueryMathTest { String studentTable; @Before public void setUp() { studentTable = DBUtility.getTableNameByClassName(Student.class.getName()); } @Test public void testCount() { int result = LitePal.count(Student.class); int realResult = -100; Cursor cursor = LitePal.findBySQL("select count(1) from " + studentTable); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); result = LitePal.where("id > ?", "99").count(studentTable); cursor = LitePal.findBySQL("select count(1) from " + studentTable + " where id > ?", "99"); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); try { LitePal.count("nosuchtable"); fail(); } catch (Exception ignored) { } } @Test public void testAverage() { double result = LitePal.average(Student.class, "age"); double realResult = -100; Cursor cursor = LitePal.findBySQL("select avg(age) from " + studentTable); if (cursor.moveToFirst()) { realResult = cursor.getDouble(0); } cursor.close(); assertEquals(realResult, result); result = LitePal.where("id > ?", "99").average(studentTable, "age"); cursor = LitePal.findBySQL("select avg(age) from " + studentTable + " where id > ?", "99"); if (cursor.moveToFirst()) { realResult = cursor.getDouble(0); } cursor.close(); assertEquals(realResult, result); try { LitePal.average(Student.class, "nosuchcolumn"); fail(); } catch (Exception e) { e.printStackTrace(); } } @Test public void testMax() { int result = LitePal.max(Student.class, "age", Integer.TYPE); int realResult = -100; Cursor cursor = LitePal.findBySQL("select max(age) from " + studentTable); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); result = LitePal.where("age < ?", "20").max(studentTable, "age", Integer.TYPE); cursor = LitePal.findBySQL("select max(age) from " + studentTable + " where age < ?", "20"); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); } @Test public void testMin() { int result = LitePal.min(Student.class, "age", Integer.TYPE); int realResult = -100; Cursor cursor = LitePal.findBySQL("select min(age) from " + studentTable); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); result = LitePal.where("age > ?", "10").min(studentTable, "age", Integer.TYPE); cursor = LitePal.findBySQL("select min(age) from " + studentTable + " where age > ?", "10"); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); } @Test public void testSum() { int result = LitePal.sum(Student.class, "age", Integer.TYPE); int realResult = -100; Cursor cursor = LitePal.findBySQL("select sum(age) from " + studentTable); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); result = LitePal.where("age > ?", "15").sum(studentTable, "age", Integer.TYPE); cursor = LitePal.findBySQL("select sum(age) from " + studentTable + " where age > ?", "15"); if (cursor.moveToFirst()) { realResult = cursor.getInt(0); } cursor.close(); assertEquals(realResult, result); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/Many2ManySaveTest.java ================================================ package com.litepaltest.test.crud.save; import androidx.test.filters.SmallTest; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.junit.Test; import org.litepal.crud.LitePalSupport; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import com.litepaltest.test.LitePalTestCase; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @SmallTest public class Many2ManySaveTest extends LitePalTestCase { private Student danny; private Student mick; private Teacher cam; private Teacher jack; private void init() { danny = new Student(); danny.setName("Danny"); danny.setAge(14); mick = new Student(); mick.setName("Mick"); mick.setAge(13); cam = new Teacher(); cam.setTeacherName("Cam"); cam.setAge(33); cam.setSex(true); cam.setTeachYears(5); jack = new Teacher(); jack.setTeacherName("Jack"); jack.setAge(36); jack.setSex(false); jack.setTeachYears(11); } private void buildBidirectionalAssociation() { danny.getTeachers().add(jack); danny.getTeachers().add(cam); mick.getTeachers().add(jack); mick.getTeachers().add(cam); cam.getStudents().add(danny); cam.getStudents().add(mick); jack.getStudents().add(danny); jack.getStudents().add(mick); } private void buildUnidirectionalAssociation() { if (Math.random() >= 0.5) { danny.getTeachers().add(jack); danny.getTeachers().add(cam); mick.getTeachers().add(jack); mick.getTeachers().add(cam); } else { cam.getStudents().add(danny); cam.getStudents().add(mick); jack.getStudents().add(danny); jack.getStudents().add(mick); } } private List getModelList() { List list = new ArrayList<>(); list.add(jack); list.add(danny); list.add(cam); list.add(mick); return list; } private void saveAllByRandom() { List modelList = getModelList(); while (!modelList.isEmpty()) { Random rand = new Random(); int index = rand.nextInt(modelList.size()); LitePalSupport model = modelList.remove(index); model.save(); } } @Test public void testCase1() { init(); buildBidirectionalAssociation(); saveAllByRandom(); assertTrue(isDataExists(getTableName(danny), danny.getId())); assertTrue(isDataExists(getTableName(mick), mick.getId())); assertTrue(isDataExists(getTableName(cam), cam.getId())); assertTrue(isDataExists(getTableName(jack), jack.getId())); assertM2M(getTableName(danny), getTableName(cam), danny.getId(), cam.getId()); assertM2M(getTableName(danny), getTableName(jack), danny.getId(), jack.getId()); assertM2M(getTableName(mick), getTableName(cam), mick.getId(), cam.getId()); assertM2M(getTableName(mick), getTableName(jack), mick.getId(), jack.getId()); } @Test public void testCase2() { init(); buildBidirectionalAssociation(); danny.save(); jack.save(); cam.save(); assertTrue(isDataExists(getTableName(danny), danny.getId())); assertFalse(isDataExists(getTableName(mick), mick.getId())); assertTrue(isDataExists(getTableName(cam), cam.getId())); assertTrue(isDataExists(getTableName(jack), jack.getId())); assertM2M(getTableName(danny), getTableName(cam), danny.getId(), cam.getId()); assertM2M(getTableName(danny), getTableName(jack), danny.getId(), jack.getId()); assertM2MFalse(getTableName(mick), getTableName(cam), mick.getId(), cam.getId()); assertM2MFalse(getTableName(mick), getTableName(jack), mick.getId(), jack.getId()); } @Test public void testCase3() { init(); buildBidirectionalAssociation(); jack.save(); cam.save(); assertFalse(isDataExists(getTableName(danny), danny.getId())); assertFalse(isDataExists(getTableName(mick), mick.getId())); assertTrue(isDataExists(getTableName(cam), cam.getId())); assertTrue(isDataExists(getTableName(jack), jack.getId())); assertM2MFalse(getTableName(danny), getTableName(cam), danny.getId(), cam.getId()); assertM2MFalse(getTableName(danny), getTableName(jack), danny.getId(), jack.getId()); assertM2MFalse(getTableName(mick), getTableName(cam), mick.getId(), cam.getId()); assertM2MFalse(getTableName(mick), getTableName(jack), mick.getId(), jack.getId()); } @Test public void testCase4() { init(); buildUnidirectionalAssociation(); saveAllByRandom(); assertTrue(isDataExists(getTableName(danny), danny.getId())); assertTrue(isDataExists(getTableName(mick), mick.getId())); assertTrue(isDataExists(getTableName(cam), cam.getId())); assertTrue(isDataExists(getTableName(jack), jack.getId())); assertM2M(getTableName(danny), getTableName(cam), danny.getId(), cam.getId()); assertM2M(getTableName(danny), getTableName(jack), danny.getId(), jack.getId()); assertM2M(getTableName(mick), getTableName(cam), mick.getId(), cam.getId()); assertM2M(getTableName(mick), getTableName(jack), mick.getId(), jack.getId()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/Many2OneBiSaveTest.java ================================================ package com.litepaltest.test.crud.save; import androidx.test.filters.SmallTest; import java.util.HashSet; import java.util.Set; import com.litepaltest.model.Classroom; import com.litepaltest.model.Student; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import static junit.framework.TestCase.assertTrue; @SmallTest public class Many2OneBiSaveTest extends LitePalTestCase { private Classroom c1; private Student s1; private Student s2; public void init() { c1 = new Classroom(); c1.setName("Computer room"); s1 = new Student(); s1.setName("Tom"); s2 = new Student(); s2.setName("Lily"); } @Test public void testCase1() { init(); Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); c1.save(); s1.save(); s2.save(); assertFK(c1, s1, s2); } @Test public void testCase2() { init(); Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); s1.save(); s2.save(); c1.save(); assertFK(c1, s1, s2); } @Test public void testCase3() { init(); Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); s2.save(); c1.save(); s1.save(); assertFK(c1, s1, s2); } @Test public void testCase4() { init(); s1.setClassroom(c1); s2.setClassroom(c1); c1.save(); s1.save(); s2.save(); assertFK(c1, s1, s2); } @Test public void testCase5() { init(); s1.setClassroom(c1); s2.setClassroom(c1); s1.save(); s2.save(); c1.save(); assertFK(c1, s1, s2); } @Test public void testCase6() { init(); s1.setClassroom(c1); s2.setClassroom(c1); s1.save(); c1.save(); s2.save(); assertFK(c1, s1, s2); } @Test public void testCase7() { init(); s1.setClassroom(c1); s2.setClassroom(c1); Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); c1.save(); s1.save(); s2.save(); assertFK(c1, s1, s2); } @Test public void testCase8() { init(); s1.setClassroom(c1); s2.setClassroom(c1); Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); s1.save(); s2.save(); c1.save(); assertFK(c1, s1, s2); } @Test public void testCase9() { init(); s1.setClassroom(c1); s2.setClassroom(c1); Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); s1.save(); c1.save(); s2.save(); assertFK(c1, s1, s2); } @Test public void testCase10() { init(); s1 = null; s2 = null; Set ss = new HashSet<>(); ss.add(s1); ss.add(s2); c1.setStudentCollection(ss); c1.save(); isDataExists(getTableName(c1), c1.get_id()); init(); c1 = null; s1.setClassroom(c1); s2.setClassroom(c1); s1.save(); isDataExists(getTableName(s1), s1.getId()); s2.save(); isDataExists(getTableName(s2), s2.getId()); } private void assertFK(Classroom c1, Student s1, Student s2) { assertTrue(isFKInsertCorrect(getTableName(c1), getTableName(s1), c1.get_id(), s1.getId())); assertTrue(isFKInsertCorrect(getTableName(c1), getTableName(s2), c1.get_id(), s2.getId())); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/Many2OneUniSaveTest.java ================================================ package com.litepaltest.test.crud.save; import androidx.test.filters.SmallTest; import com.litepaltest.model.Classroom; import com.litepaltest.model.Teacher; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import static junit.framework.TestCase.assertTrue; @SmallTest public class Many2OneUniSaveTest extends LitePalTestCase { private Classroom c1; private Teacher t1; private Teacher t2; public void init() { c1 = new Classroom(); c1.setName("Music room"); t1 = new Teacher(); t1.setTeacherName("John"); t1.setAge(25); t2 = new Teacher(); t2.setTeacherName("Sean"); t2.setAge(35); } @Test public void testCase1() { init(); c1.getTeachers().add(t1); c1.getTeachers().add(t2); c1.save(); t1.save(); t2.save(); assertFK(c1, t1, t2); } @Test public void testCase2() { init(); c1.getTeachers().add(t1); c1.getTeachers().add(t2); t1.save(); t2.save(); c1.save(); assertFK(c1, t1, t2); } @Test public void testCase3() { init(); c1.getTeachers().add(t1); c1.getTeachers().add(t2); t1.save(); c1.save(); t2.save(); assertFK(c1, t1, t2); } @Test public void testCase4() { init(); t1 = null; t2 = null; c1.getTeachers().add(t1); c1.getTeachers().add(t2); c1.save(); isDataExists(getTableName(c1), c1.get_id()); } private void assertFK(Classroom c1, Teacher t1, Teacher t2) { assertTrue(isFKInsertCorrect(getTableName(c1), getTableName(t1), c1.get_id(), t1.getId())); assertTrue(isFKInsertCorrect(getTableName(c1), getTableName(t2), c1.get_id(), t2.getId())); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/One2OneBiSaveTest.java ================================================ package com.litepaltest.test.crud.save; import androidx.test.filters.SmallTest; import com.litepaltest.model.IdCard; import com.litepaltest.model.Student; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import static junit.framework.TestCase.assertTrue; @SmallTest public class One2OneBiSaveTest extends LitePalTestCase { private Student s; private IdCard i; private void init() { s = new Student(); s.setName("Jimmy"); s.setAge(18); i = new IdCard(); i.setNumber("9997777112"); i.setAddress("Nanjing road"); } @Test public void testO2OBiSaveStudentFirst() { init(); s.setIdcard(i); i.setStudent(s); s.save(); i.save(); assertFK(s, i); } @Test public void testO2OBiSaveIdCardFirst() { init(); s.setIdcard(i); i.setStudent(s); i.save(); s.save(); assertFK(s, i); } @Test public void testO2OBiBuildNullAssocations() { init(); s.setIdcard(null); i.setStudent(null); i.save(); s.save(); isDataExists(getTableName(s), s.getId()); isDataExists(getTableName(i), i.getId()); } @Test public void testO2OBiBuildUniAssociationsSaveStudentFirst() { init(); s.setIdcard(i); s.save(); i.save(); assertFK(s, i); } @Test public void testO2OBiBuildUniAssociationsSaveIdCardFirst() { init(); s.setIdcard(i); i.save(); s.save(); assertFK(s, i); } private void assertFK(Student s, IdCard i) { assertTrue(isFKInsertCorrect(getTableName(s), getTableName(i), s.getId(), i.getId())); assertTrue(isFKInsertCorrect(getTableName(i), getTableName(s), i.getId(), s.getId())); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/One2OneUniSaveTest.java ================================================ package com.litepaltest.test.crud.save; import androidx.test.filters.SmallTest; import com.litepaltest.model.IdCard; import com.litepaltest.model.Teacher; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import static junit.framework.TestCase.assertTrue; @SmallTest public class One2OneUniSaveTest extends LitePalTestCase { private Teacher t; private IdCard i; private void init() { t = new Teacher(); t.setTeacherName("Will"); t.setTeachYears(10); t.setAge(40); i = new IdCard(); i.setNumber("9997777121"); i.setAddress("shanghai road"); } @Test public void testSaveIdCardFirst() { init(); t.setIdCard(i); i.save(); t.save(); assertFK(t, i); } @Test public void testSaveTeacherFirst() { init(); t.setIdCard(i); t.save(); i.save(); assertFK(t, i); } @Test public void testBuildNullAssociations() { init(); t.setIdCard(null); t.save(); i.save(); isDataExists(getTableName(t), t.getId()); isDataExists(getTableName(i), i.getId()); } private void assertFK(Teacher t, IdCard i) { assertTrue(isFKInsertCorrect(getTableName(t), getTableName(i), t.getId(), i.getId())); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/SaveAllKotlinTest.kt ================================================ package com.litepaltest.test.crud.save import androidx.test.filters.SmallTest import com.litepaltest.model.* import junit.framework.TestCase import junit.framework.TestCase.assertEquals import org.junit.Before import org.junit.Test import org.litepal.LitePal.find import org.litepal.LitePal.findBySQL import org.litepal.LitePal.where import org.litepal.extension.saveAll import org.litepal.util.DBUtility import java.util.* @SmallTest class SaveAllKotlinTest { var classroomTable: String? = null var studentTable: String? = null @Before fun setUp() { classroomTable = DBUtility.getTableNameByClassName(Classroom::class.java.name) studentTable = DBUtility.getTableNameByClassName(Student::class.java.name) } @Test fun testSaveAll() { val cellList: MutableList = ArrayList() for (i in 0..49) { val cellPhone = Cellphone() cellPhone.setBrand("Samsung unique") cellPhone.price = Math.random() cellPhone.serial = UUID.randomUUID().toString() cellList.add(cellPhone) } TestCase.assertTrue(cellList.saveAll()) for (cell in cellList) { TestCase.assertTrue(cell.isSaved) } } @Test fun testSaveAllWithM2OOnOneSide() { val classroom = Classroom() classroom.name = "Music room" for (i in 0..49) { val student = Student() student.name = "Tom" student.age = Random().nextInt(20) classroom.studentCollection.add(student) } TestCase.assertTrue(classroom.studentCollection.saveAll()) classroom.save() val list = where(classroomTable + "_id = ?", classroom._id.toString()).find(Student::class.java) assertEquals(50, list.size) } @Test fun testSaveAllWithM2OOnManySide() { val classroom = Classroom() classroom.name = "English room" val studentList: MutableList = ArrayList() for (i in 0..49) { val student = Student() student.name = "Tom" student.age = Random().nextInt(20) student.classroom = classroom studentList.add(student) } TestCase.assertTrue(studentList.saveAll()) classroom.save() val list = where(classroomTable + "_id = ?", classroom._id.toString()).find(Student::class.java) assertEquals(50, list.size) } @Test fun testSaveAllWithO2O() { val idcardList: MutableList = ArrayList() val studentList: MutableList = ArrayList() for (i in 0..49) { val idcard = IdCard() idcard.number = Random().nextInt(2000000).toString() val student = Student() student.name = "Jim" student.age = Random().nextInt(20) student.idcard = idcard idcardList.add(idcard) studentList.add(student) } TestCase.assertTrue(idcardList.saveAll()) TestCase.assertTrue(studentList.saveAll()) for (student in studentList) { val result = where(studentTable + "_id=?", student.id.toString()).find(IdCard::class.java) assertEquals(1, result.size) } } @Test fun testSaveAllWithM2M() { val studentList: MutableList = ArrayList() val teacherList: MutableList = ArrayList() for (i in 0..49) { val teacher = Teacher() teacher.teacherName = "Lucy" teacher.teachYears = Random().nextInt(10) teacherList.add(teacher) } for (i in 0..49) { val student = Student() student.name = "Timmy" student.age = Random().nextInt(20) val index1 = Random().nextInt(50) student.teachers.add(teacherList[index1]) var index2 = index1 while (index2 == index1) { index2 = Random().nextInt(50) } student.teachers.add(teacherList[index2]) var index3 = index2 while (index3 == index2 || index3 == index1) { index3 = Random().nextInt(50) } student.teachers.add(teacherList[index3]) studentList.add(student) } TestCase.assertTrue(studentList.saveAll()) TestCase.assertTrue(teacherList.saveAll()) val studentTable = DBUtility.getTableNameByClassName(Student::class.java.name) val teacherTable = DBUtility.getTableNameByClassName(Teacher::class.java.name) val tableName = DBUtility.getIntermediateTableName(studentTable, teacherTable) for (student in studentList) { val cursor = findBySQL( "select * from " + tableName + " where " + studentTable + "_id=?", student.id.toString()) assertEquals(3, cursor.count) cursor.close() } } @Test fun testSaveAllGenericData() { val classroomList: MutableList = ArrayList() for (i in 0..49) { val classroom = Classroom() classroom.name = "classroom $i" for (j in 0..19) { classroom.news.add("news $i") } for (k in 0..12) { classroom.numbers.add(k) } classroomList.add(classroom) } TestCase.assertTrue(classroomList.saveAll()) assertEquals(50, classroomList.size) for (classroom in classroomList) { TestCase.assertTrue(classroom.isSaved) val c = find(Classroom::class.java, classroom._id.toLong()) TestCase.assertTrue(c.name.startsWith("classroom")) assertEquals(20, c.news.size) assertEquals(13, c.numbers.size) } } @Test fun testSaveAllFailed() { val cellphones: MutableList = ArrayList() val serial = UUID.randomUUID().toString() for (i in 0..19) { val cellphone = Cellphone() cellphone.setBrand("Apple") cellphone.serial = serial + i % 10 // serial is unique, so this should save failed cellphones.add(cellphone) } TestCase.assertFalse(cellphones.saveAll()) val list = where("serial like ?", "$serial%").find(Cellphone::class.java) TestCase.assertTrue(list.isEmpty()) } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/SaveAllTest.java ================================================ package com.litepaltest.test.crud.save; import android.database.Cursor; import androidx.test.filters.SmallTest; import com.litepaltest.model.Cellphone; import com.litepaltest.model.Classroom; import com.litepaltest.model.IdCard; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import org.litepal.util.DBUtility; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @SmallTest public class SaveAllTest { String classroomTable; String studentTable; @Before public void setUp() { classroomTable = DBUtility.getTableNameByClassName(Classroom.class.getName()); studentTable = DBUtility.getTableNameByClassName(Student.class.getName()); } @Test public void testSaveAll() { List cellList = new ArrayList<>(); for (int i = 0; i < 50; i++) { Cellphone cellPhone = new Cellphone(); cellPhone.setBrand("Samsung unique"); cellPhone.setPrice(Math.random()); cellPhone.setSerial(UUID.randomUUID().toString()); cellList.add(cellPhone); } assertTrue(LitePal.saveAll(cellList)); for (Cellphone cell : cellList) { assertTrue(cell.isSaved()); } } @Test public void testSaveAllWithM2OOnOneSide() { Classroom classroom = new Classroom(); classroom.setName("Music room"); for (int i = 0; i < 50; i++) { Student student = new Student(); student.setName("Tom"); student.setAge(new Random().nextInt(20)); classroom.getStudentCollection().add(student); } assertTrue(LitePal.saveAll(classroom.getStudentCollection())); classroom.save(); List list = LitePal.where(classroomTable + "_id = ?", String.valueOf(classroom.get_id())).find(Student.class); assertEquals(50, list.size()); } @Test public void testSaveAllWithM2OOnManySide() { Classroom classroom = new Classroom(); classroom.setName("English room"); List studentList = new ArrayList<>(); for (int i = 0; i < 50; i++) { Student student = new Student(); student.setName("Tom"); student.setAge(new Random().nextInt(20)); student.setClassroom(classroom); studentList.add(student); } assertTrue(LitePal.saveAll(studentList)); classroom.save(); List list = LitePal.where(classroomTable + "_id = ?", String.valueOf(classroom.get_id())).find(Student.class); assertEquals(50, list.size()); } @Test public void testSaveAllWithO2O() { List idcardList = new ArrayList<>(); List studentList = new ArrayList<>(); for (int i = 0; i < 50; i++) { IdCard idcard = new IdCard(); idcard.setNumber(String.valueOf(new Random().nextInt(2000000))); Student student = new Student(); student.setName("Jim"); student.setAge(new Random().nextInt(20)); student.setIdcard(idcard); idcardList.add(idcard); studentList.add(student); } assertTrue(LitePal.saveAll(idcardList)); assertTrue(LitePal.saveAll(studentList)); for (Student student : studentList) { List result = LitePal .where(studentTable + "_id=?", String.valueOf(student.getId())).find(IdCard.class); assertEquals(1, result.size()); } } @Test public void testSaveAllWithM2M() { List studentList = new ArrayList<>(); List teacherList = new ArrayList<>(); for (int i = 0; i < 50; i++) { Teacher teacher = new Teacher(); teacher.setTeacherName("Lucy"); teacher.setTeachYears(new Random().nextInt(10)); teacherList.add(teacher); } for (int i = 0; i < 50; i++) { Student student = new Student(); student.setName("Timmy"); student.setAge(new Random().nextInt(20)); int index1 = new Random().nextInt(50); student.getTeachers().add(teacherList.get(index1)); int index2 = index1; while (index2 == index1) { index2 = new Random().nextInt(50); } student.getTeachers().add(teacherList.get(index2)); int index3 = index2; while (index3 == index2 || index3 == index1) { index3 = new Random().nextInt(50); } student.getTeachers().add(teacherList.get(index3)); studentList.add(student); } assertTrue(LitePal.saveAll(studentList)); assertTrue(LitePal.saveAll(teacherList)); String studentTable = DBUtility.getTableNameByClassName(Student.class.getName()); String teacherTable = DBUtility.getTableNameByClassName(Teacher.class.getName()); String tableName = DBUtility.getIntermediateTableName(studentTable, teacherTable); for (Student student : studentList) { Cursor cursor = LitePal.findBySQL( "select * from " + tableName + " where " + studentTable + "_id=?", String.valueOf(student.getId())); assertEquals(3, cursor.getCount()); cursor.close(); } } @Test public void testSaveAllGenericData() { List classroomList = new ArrayList<>(); for (int i = 0; i < 50; i++) { Classroom classroom = new Classroom(); classroom.setName("classroom " + i); for (int j = 0; j < 20; j++) { classroom.getNews().add("news " + i); } for (int k = 0; k < 13; k++) { classroom.getNumbers().add(k); } classroomList.add(classroom); } assertTrue(LitePal.saveAll(classroomList)); assertEquals(50, classroomList.size()); for (Classroom classroom : classroomList) { assertTrue(classroom.isSaved()); Classroom c = LitePal.find(Classroom.class, classroom.get_id()); assertTrue(c.getName().startsWith("classroom")); assertEquals(20, c.getNews().size()); assertEquals(13, c.getNumbers().size()); } } @Test public void testSaveAllFailed() { List cellphones = new ArrayList<>(); String serial = UUID.randomUUID().toString(); for (int i = 0; i < 20; i++) { Cellphone cellphone = new Cellphone(); cellphone.setBrand("Apple"); cellphone.setSerial(serial + (i % 10)); // serial is unique, so this should save failed cellphones.add(cellphone); } assertFalse(LitePal.saveAll(cellphones)); List list = LitePal.where("serial like ?", serial + "%").find(Cellphone.class); assertTrue(list.isEmpty()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/save/SaveTest.java ================================================ package com.litepaltest.test.crud.save; import androidx.test.filters.SmallTest; import com.litepaltest.model.Book; import com.litepaltest.model.Cellphone; import com.litepaltest.model.Classroom; import com.litepaltest.model.Computer; import com.litepaltest.model.IdCard; import com.litepaltest.model.Product; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import com.litepaltest.model.WeChatMessage; import com.litepaltest.model.WeiboMessage; import com.litepaltest.test.LitePalTestCase; import org.junit.Test; import org.litepal.LitePal; import java.util.ArrayList; import java.util.List; import java.util.UUID; import static junit.framework.Assert.assertNotNull; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; @SmallTest public class SaveTest extends LitePalTestCase { @Test public void testSave() { Cellphone cell = new Cellphone(); cell.setBrand("iPhone"); cell.setPrice(4998.01); cell.setInStock('Y'); cell.setSerial(UUID.randomUUID().toString()); assertTrue(cell.save()); assertTrue(isDataExists(getTableName(cell), cell.getId())); } @Test public void testSaveWithConstructors() { Computer computer = new Computer("asus", 699.00); assertTrue(computer.save()); assertTrue(isDataExists(getTableName(computer), computer.getId())); Computer c = getComputer(computer.getId()); assertEquals("asus", c.getBrand()); assertEquals(699.00, c.getPrice()); Computer cc = LitePal.find(Computer.class, computer.getId()); assertEquals("asus", cc.getBrand()); assertEquals(699.00, cc.getPrice()); Product p = new Product(null); p.setBrand("apple"); p.setPrice(1222.33); p.save(); } @Test public void testSaveAfterDelete() { Cellphone cell = new Cellphone(); cell.setBrand("iPhone"); cell.setPrice(4998.01); cell.setInStock('Y'); cell.setSerial(UUID.randomUUID().toString()); assertTrue(cell.save()); assertTrue(isDataExists(getTableName(cell), cell.getId())); assertTrue(cell.delete() > 0); assertTrue(cell.save()); assertTrue(isDataExists(getTableName(cell), cell.getId())); Student stu = new Student(); stu.setName("Jimmy"); IdCard idcard = new IdCard(); idcard.setAddress("Washington"); idcard.setNumber("123456"); idcard.setStudent(stu); stu.setIdcard(idcard); stu.save(); idcard.save(); assertTrue(isDataExists(getTableName(stu), stu.getId())); assertTrue(isDataExists(getTableName(idcard), idcard.getId())); stu.delete(); assertFalse(isDataExists(getTableName(stu), stu.getId())); assertFalse(isDataExists(getTableName(idcard), idcard.getId())); stu.save(); idcard.save(); assertTrue(isDataExists(getTableName(stu), stu.getId())); assertTrue(isDataExists(getTableName(idcard), idcard.getId())); Student danny = new Student(); danny.setName("Danny"); danny.setAge(14); Teacher cam = new Teacher(); cam.setTeacherName("Cam"); cam.setAge(33); cam.setSex(true); cam.setTeachYears(5); Teacher jack = new Teacher(); jack.setTeacherName("Jack"); jack.setAge(36); jack.setSex(false); jack.setTeachYears(11); danny.getTeachers().add(jack); danny.getTeachers().add(cam); cam.getStudents().add(danny); jack.getStudents().add(danny); danny.save(); cam.save(); jack.save(); assertTrue(isDataExists(getTableName(danny), danny.getId())); assertTrue(isDataExists(getTableName(cam), cam.getId())); assertTrue(isDataExists(getTableName(jack), jack.getId())); danny.delete(); assertFalse(isDataExists(getTableName(danny), danny.getId())); assertTrue(isDataExists(getTableName(cam), cam.getId())); assertTrue(isDataExists(getTableName(jack), jack.getId())); danny.save(); assertTrue(isDataExists(getTableName(danny), danny.getId())); assertEquals(danny.getTeachers().size(), 2); Classroom c = new Classroom(); c.setName("test classroom"); Student s = new Student(); s.setName("Tom"); s.setClassroom(c); Student s2 = new Student(); s2.setName("Tom"); s2.setClassroom(c); assertTrue(c.save()); assertTrue(s.save()); assertTrue(s2.save()); assertTrue(isDataExists(getTableName(c), c.get_id())); assertTrue(isDataExists(getTableName(s), s.getId())); assertTrue(isDataExists(getTableName(s), s2.getId())); c.delete(); assertFalse(isDataExists(getTableName(c), c.get_id())); assertFalse(isDataExists(getTableName(s), s.getId())); assertFalse(isDataExists(getTableName(s), s2.getId())); c.save(); s.save(); s2.save(); assertTrue(isDataExists(getTableName(c), c.get_id())); assertTrue(isDataExists(getTableName(s), s.getId())); assertTrue(isDataExists(getTableName(s), s2.getId())); } @Test public void testSaveInheritModels() { WeChatMessage weChatMessage = new WeChatMessage(); weChatMessage.setFriend("Tom"); weChatMessage.setContent("Hello nice to meet you"); weChatMessage.setTitle("Greeting message"); weChatMessage.setType(1); assertTrue(weChatMessage.save()); assertTrue(weChatMessage.getId() > 0); WeChatMessage message1 = LitePal.find(WeChatMessage.class, weChatMessage.getId()); assertEquals("Tom", message1.getFriend()); assertEquals("Hello nice to meet you", message1.getContent()); assertNull(message1.getTitle()); assertEquals(1, message1.getType()); WeiboMessage weiboMessage = new WeiboMessage(); weiboMessage.setType(2); weiboMessage.setTitle("Following message"); weiboMessage.setContent("Something big happens"); weiboMessage.setFollower("Jimmy"); weiboMessage.setNumber(123456); assertTrue(weiboMessage.save()); assertTrue(weiboMessage.getId() > 0); } @Test public void testSaveInheritModelsWithAssociations() { Cellphone cellphone = new Cellphone(); cellphone.setBrand("iPhone 7"); cellphone.setInStock('N'); cellphone.setPrice(6999.99); cellphone.setSerial(UUID.randomUUID().toString()); cellphone.setMac("ff:3d:4a:99:76"); cellphone.save(); WeChatMessage weChatMessage = new WeChatMessage(); weChatMessage.setFriend("Tom"); weChatMessage.setContent("Hello nice to meet you"); weChatMessage.setTitle("Greeting message"); weChatMessage.setType(1); assertTrue(weChatMessage.save()); assertTrue(weChatMessage.getId() > 0); WeChatMessage message1 = LitePal.find(WeChatMessage.class, weChatMessage.getId()); assertEquals("Tom", message1.getFriend()); assertEquals("Hello nice to meet you", message1.getContent()); assertNull(message1.getTitle()); assertEquals(1, message1.getType()); WeiboMessage weiboMessage = new WeiboMessage(); weiboMessage.setType(2); weiboMessage.setTitle("Following message"); weiboMessage.setContent("Something big happens"); weiboMessage.setFollower("Jimmy"); weiboMessage.setNumber(123456); weiboMessage.setCellphone(cellphone); assertTrue(weiboMessage.save()); assertTrue(weiboMessage.getId() > 0); WeiboMessage message2 = LitePal.find(WeiboMessage.class, weiboMessage.getId(), true); Cellphone result = message2.getCellphone(); assertEquals(cellphone.getId(), result.getId()); assertEquals(cellphone.getBrand(), result.getBrand()); assertEquals(cellphone.getInStock(), result.getInStock()); assertEquals(cellphone.getPrice(), result.getPrice()); assertEquals(cellphone.getSerial(), result.getSerial()); assertEquals(cellphone.getMac(), result.getMac()); } @Test public void testSaveGenericData() { Classroom classroom = new Classroom(); classroom.setName("classroom1"); classroom.getNews().add("news1"); classroom.getNews().add("news2"); classroom.getNews().add("news3"); List numbers = new ArrayList<>(); numbers.add(1); numbers.add(2); numbers.add(3); numbers.add(4); classroom.setNumbers(numbers); classroom.save(); Classroom c = LitePal.find(Classroom.class, classroom.get_id()); assertEquals("classroom1", c.getName()); assertEquals(3, c.getNews().size()); assertEquals(4, c.getNumbers().size()); for (String news : c.getNews()) { assertTrue(news.equals("news1") || news.equals("news2") || news.equals("news3")); } for (int number : c.getNumbers()) { assertTrue(number == 1 || number == 2 || number == 3 || number == 4); } } @Test public void testSaveLongMaximumNumber() { IdCard idCard = new IdCard(); idCard.setSerial(Long.MAX_VALUE); idCard.setAddress("abczyx"); assertTrue(idCard.save()); IdCard idCardFromDB = LitePal.find(IdCard.class, idCard.getId()); assertEquals(Long.MAX_VALUE, idCardFromDB.getSerial()); } @Test public void testNullValue() { Book book = new Book(); book.setBookName("First Line of Android"); assertTrue(book.save()); Book bookFromDB = LitePal.find(Book.class, book.getId()); assertNotNull(bookFromDB); assertNull(bookFromDB.getPages()); // pages should be null cause it's Integer type and assign no value. book.setPages(123); // assign pages assertTrue(book.save()); bookFromDB = LitePal.find(Book.class, book.getId()); assertNotNull(bookFromDB); assertNotNull(bookFromDB.getPages()); // now we should be pages value. assertEquals(Integer.valueOf(123), book.getPages()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/transaction/TransactionKotlinTest.kt ================================================ package com.litepaltest.test.crud.transaction import android.content.ContentValues import androidx.test.filters.SmallTest import com.litepaltest.model.* import com.litepaltest.test.LitePalTestCase import junit.framework.TestCase import org.junit.Assert import org.junit.Test import org.litepal.LitePal import org.litepal.extension.find import org.litepal.extension.runInTransaction import java.lang.NullPointerException import java.util.* @SmallTest class TransactionKotlinTest : LitePalTestCase() { @Test fun testTransactionForSave() { val book = Book() LitePal.runInTransaction { book.bookName = "First Line of Android" book.pages = 700 Assert.assertTrue(book.save()) val bookFromDb = LitePal.find(Book::class.java, book.id) Assert.assertEquals("First Line of Android", bookFromDb.bookName) Assert.assertEquals(700L, bookFromDb.pages.toInt().toLong()) false } Assert.assertTrue(book.isSaved) val bookFromDb = LitePal.find(Book::class.java, book.id) Assert.assertNull(bookFromDb) } @Test fun testTransactionForSaveAll() { val serial = UUID.randomUUID().toString() val weiboMessage = WeiboMessage() LitePal.runInTransaction { weiboMessage.follower = "nobody" val saveResult = weiboMessage.save() val cellphones: MutableList = ArrayList() for (i in 0..19) { val cellphone = Cellphone() cellphone.setBrand("Apple") cellphone.serial = serial + i % 10 // serial is unique, so this should save failed cellphone.messages.add(weiboMessage) cellphones.add(cellphone) } val saveAllResult = LitePal.saveAll(cellphones) saveResult && saveAllResult } Assert.assertTrue(weiboMessage.isSaved) val messageFromDb = LitePal.find(WeiboMessage::class.java, weiboMessage.id.toLong()) Assert.assertNull(messageFromDb) val list = LitePal.where("serial like ?", "$serial%").find(Cellphone::class.java) TestCase.assertTrue(list.isEmpty()) } @Test fun testTransactionForUpdate() { val teacher = Teacher() teacher.teacherName = "Tony" teacher.teachYears = 3 teacher.age = 23 teacher.isSex = false Assert.assertTrue(teacher.save()) LitePal.runInTransaction { val values = ContentValues() values.put("TeachYears", 13) val rows = LitePal.update(Teacher::class.java, values, teacher.id.toLong()) Assert.assertEquals(1, rows.toLong()) val teacherFromDb = LitePal.find(Teacher::class.java, teacher.id.toLong()) Assert.assertEquals(13, teacherFromDb.teachYears.toLong()) // not set transaction successful false } val teacherFromDb = LitePal.find(Teacher::class.java, teacher.id.toLong()) Assert.assertEquals(3, teacherFromDb.teachYears.toLong()) } @Test fun testTransactionForDelete() { val tony = Student() tony.name = "Tony" tony.age = 23 tony.save() val studentId = tony.id LitePal.runInTransaction { val rowsAffected = tony.delete() Assert.assertEquals(1, rowsAffected.toLong()) val studentFromDb = LitePal.find(studentId.toLong()) Assert.assertNull(studentFromDb) // not set transaction successful false } val studentFromDb = LitePal.find(studentId.toLong()) Assert.assertNotNull(studentFromDb) Assert.assertEquals("Tony", studentFromDb!!.name) Assert.assertEquals(23, studentFromDb.age.toLong()) } @Test fun testTransactionForCRUD() { var lastId = -1 LitePal.runInTransaction { val tony = Student() tony.name = "Tony" tony.age = 23 tony.save() val studentId = tony.id var studentFromDb = LitePal.find(studentId.toLong()) Assert.assertNotNull(studentFromDb) Assert.assertEquals("Tony", studentFromDb!!.name) Assert.assertEquals(23, studentFromDb.age.toLong()) val updateModel = Student() updateModel.age = 25 var rowsAffected = updateModel.update(studentId.toLong()) Assert.assertEquals(1, rowsAffected.toLong()) studentFromDb = LitePal.find(Student::class.java, studentId.toLong()) Assert.assertEquals(25, studentFromDb.age.toLong()) rowsAffected = tony.delete() Assert.assertEquals(1, rowsAffected.toLong()) studentFromDb = LitePal.find(Student::class.java, studentId.toLong()) Assert.assertNull(studentFromDb) Assert.assertTrue(tony.save()) studentFromDb = LitePal.find(Student::class.java, tony.id.toLong()) Assert.assertNotNull(studentFromDb) lastId = tony.id // not set transaction successful false } val studentFromDb = LitePal.find(lastId.toLong()) Assert.assertNull(studentFromDb) } @Test fun testTransactionSuccessfulForCRUD() { var lastId = -1 LitePal.runInTransaction { val tony = Student() tony.name = "Tony" tony.age = 23 tony.save() val studentId = tony.id var studentFromDb = LitePal.find(studentId.toLong()) Assert.assertNotNull(studentFromDb) Assert.assertEquals("Tony", studentFromDb!!.name) Assert.assertEquals(23, studentFromDb.age.toLong()) val updateModel = Student() updateModel.age = 25 var rowsAffected = updateModel.update(studentId.toLong()) Assert.assertEquals(1, rowsAffected.toLong()) studentFromDb = LitePal.find(Student::class.java, studentId.toLong()) Assert.assertEquals(25, studentFromDb.age.toLong()) rowsAffected = tony.delete() Assert.assertEquals(1, rowsAffected.toLong()) studentFromDb = LitePal.find(Student::class.java, studentId.toLong()) Assert.assertNull(studentFromDb) Assert.assertTrue(tony.save()) studentFromDb = LitePal.find(Student::class.java, tony.id.toLong()) Assert.assertNotNull(studentFromDb) lastId = tony.id // not set transaction successful true } val studentFromDb = LitePal.find(lastId.toLong()) Assert.assertNotNull(studentFromDb) Assert.assertEquals("Tony", studentFromDb!!.name) Assert.assertEquals(23, studentFromDb.age.toLong()) } @Test fun testTransactionWithException() { val book = Book() LitePal.runInTransaction { book.bookName = "First Line of Android" book.pages = 700 Assert.assertTrue(book.save()) val bookFromDb = LitePal.find(Book::class.java, book.id) Assert.assertEquals("First Line of Android", bookFromDb.bookName) Assert.assertEquals(700L, bookFromDb.pages.toInt().toLong()) if (true) throw NullPointerException("just throw to fail the transaction") true } Assert.assertTrue(book.isSaved) val bookFromDb = LitePal.find(Book::class.java, book.id) Assert.assertNull(bookFromDb) } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/transaction/TransactionTest.java ================================================ package com.litepaltest.test.crud.transaction; import android.content.ContentValues; import androidx.test.filters.SmallTest; import com.litepaltest.model.Book; import com.litepaltest.model.Cellphone; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import com.litepaltest.model.WeiboMessage; import com.litepaltest.test.LitePalTestCase; import org.junit.Assert; import org.junit.Test; import org.litepal.LitePal; import java.util.ArrayList; import java.util.List; import java.util.UUID; import static junit.framework.TestCase.assertTrue; @SmallTest public class TransactionTest extends LitePalTestCase { @Test public void testTransactionForSave() { LitePal.beginTransaction(); Book book = new Book(); try { book.setBookName("First Line of Android"); book.setPages(700); Assert.assertTrue(book.save()); Book bookFromDb = LitePal.find(Book.class, book.getId()); Assert.assertEquals("First Line of Android", bookFromDb.getBookName()); Assert.assertEquals(700L, bookFromDb.getPages().intValue()); if (true) { throw new NullPointerException("Throw a exception to fail the transaction."); } LitePal.setTransactionSuccessful(); } catch (Exception e) { // do nothing } finally { LitePal.endTransaction(); } Assert.assertTrue(book.isSaved()); Book bookFromDb = LitePal.find(Book.class, book.getId()); Assert.assertNull(bookFromDb); } @Test public void testTransactionForSaveAll() { LitePal.beginTransaction(); String serial = UUID.randomUUID().toString(); WeiboMessage weiboMessage = new WeiboMessage(); try { weiboMessage.setFollower("nobody"); boolean saveResult = weiboMessage.save(); List cellphones = new ArrayList<>(); for (int i = 0; i < 20; i++) { Cellphone cellphone = new Cellphone(); cellphone.setBrand("Apple"); cellphone.setSerial(serial + (i % 10)); // serial is unique, so this should save failed cellphone.getMessages().add(weiboMessage); cellphones.add(cellphone); } boolean saveAllResult = LitePal.saveAll(cellphones); if (saveResult && saveAllResult) { LitePal.setTransactionSuccessful(); } } finally { LitePal.endTransaction(); } Assert.assertTrue(weiboMessage.isSaved()); WeiboMessage messageFromDb = LitePal.find(WeiboMessage.class, weiboMessage.getId()); Assert.assertNull(messageFromDb); List list = LitePal.where("serial like ?", serial + "%").find(Cellphone.class); assertTrue(list.isEmpty()); } @Test public void testTransactionForUpdate() { Teacher teacher = new Teacher(); teacher.setTeacherName("Tony"); teacher.setTeachYears(3); teacher.setAge(23); teacher.setSex(false); Assert.assertTrue(teacher.save()); LitePal.beginTransaction(); ContentValues values = new ContentValues(); values.put("TeachYears", 13); int rows = LitePal.update(Teacher.class, values, teacher.getId()); Assert.assertEquals(1, rows); Teacher teacherFromDb = LitePal.find(Teacher.class, teacher.getId()); Assert.assertEquals(13, teacherFromDb.getTeachYears()); // not set transaction successful LitePal.endTransaction(); teacherFromDb = LitePal.find(Teacher.class, teacher.getId()); Assert.assertEquals(3, teacherFromDb.getTeachYears()); } @Test public void testTransactionForDelete() { Student tony = new Student(); tony.setName("Tony"); tony.setAge(23); tony.save(); int studentId = tony.getId(); LitePal.beginTransaction(); int rowsAffected = tony.delete(); Assert.assertEquals(1, rowsAffected); Student studentFromDb = LitePal.find(Student.class, studentId); Assert.assertNull(studentFromDb); // not set transaction successful LitePal.endTransaction(); studentFromDb = LitePal.find(Student.class, studentId); Assert.assertNotNull(studentFromDb); Assert.assertEquals("Tony", studentFromDb.getName()); Assert.assertEquals(23, studentFromDb.getAge()); } @Test public void testTransactionForCRUD() { LitePal.beginTransaction(); Student tony = new Student(); tony.setName("Tony"); tony.setAge(23); tony.save(); int studentId = tony.getId(); Student studentFromDb = LitePal.find(Student.class, studentId); Assert.assertNotNull(studentFromDb); Assert.assertEquals("Tony", studentFromDb.getName()); Assert.assertEquals(23, studentFromDb.getAge()); Student updateModel = new Student(); updateModel.setAge(25); int rowsAffected = updateModel.update(studentId); Assert.assertEquals(1, rowsAffected); studentFromDb = LitePal.find(Student.class, studentId); Assert.assertEquals(25, studentFromDb.getAge()); rowsAffected = tony.delete(); Assert.assertEquals(1, rowsAffected); studentFromDb = LitePal.find(Student.class, studentId); Assert.assertNull(studentFromDb); Assert.assertTrue(tony.save()); studentFromDb = LitePal.find(Student.class, tony.getId()); Assert.assertNotNull(studentFromDb); // not set transaction successful LitePal.endTransaction(); studentFromDb = LitePal.find(Student.class, tony.getId()); Assert.assertNull(studentFromDb); } @Test public void testTransactionSuccessfulForCRUD() { LitePal.beginTransaction(); Student tony = new Student(); tony.setName("Tony"); tony.setAge(23); tony.save(); int studentId = tony.getId(); Student studentFromDb = LitePal.find(Student.class, studentId); Assert.assertNotNull(studentFromDb); Assert.assertEquals("Tony", studentFromDb.getName()); Assert.assertEquals(23, studentFromDb.getAge()); Student updateModel = new Student(); updateModel.setAge(25); int rowsAffected = updateModel.update(studentId); Assert.assertEquals(1, rowsAffected); studentFromDb = LitePal.find(Student.class, studentId); Assert.assertEquals(25, studentFromDb.getAge()); rowsAffected = tony.delete(); Assert.assertEquals(1, rowsAffected); studentFromDb = LitePal.find(Student.class, studentId); Assert.assertNull(studentFromDb); Assert.assertTrue(tony.save()); studentFromDb = LitePal.find(Student.class, tony.getId()); Assert.assertNotNull(studentFromDb); LitePal.setTransactionSuccessful(); LitePal.endTransaction(); studentFromDb = LitePal.find(Student.class, tony.getId()); Assert.assertNotNull(studentFromDb); Assert.assertEquals("Tony", studentFromDb.getName()); Assert.assertEquals(23, studentFromDb.getAge()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/update/UpdateUsingSaveMethodTest.java ================================================ package com.litepaltest.test.crud.update; import androidx.test.filters.SmallTest; import java.util.ArrayList; import java.util.Calendar; import java.util.LinkedList; import java.util.List; import java.util.UUID; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import org.litepal.util.DBUtility; import com.litepaltest.model.Cellphone; import com.litepaltest.model.Classroom; import com.litepaltest.model.IdCard; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import com.litepaltest.test.LitePalTestCase; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @SmallTest public class UpdateUsingSaveMethodTest extends LitePalTestCase { String classroomTable; String studentTable; String teacherTable; String idcardTable; private Classroom c1; private Classroom c2; private Student s1; private Student s2; private Student s3; private IdCard id1; private Teacher t1; private Teacher t2; @Before public void setUp() { classroomTable = DBUtility.getTableNameByClassName(Classroom.class.getName()); studentTable = DBUtility.getTableNameByClassName(Student.class.getName()); teacherTable = DBUtility.getTableNameByClassName(Teacher.class.getName()); idcardTable = DBUtility.getTableNameByClassName(IdCard.class.getName()); } private void init() { Calendar calendar = Calendar.getInstance(); c1 = new Classroom(); c1.setName("Working room"); c2 = new Classroom(); c2.setName("Resting room"); s1 = new Student(); s1.setName("Parker"); s1.setAge(18); s2 = new Student(); s2.setName("Peter"); calendar.clear(); calendar.set(1990, 9, 16, 0, 0, 0); s2.setBirthday(calendar.getTime()); s2.setAge(19); s3 = new Student(); s3.setName("Miley"); s3.setAge(16); id1 = new IdCard(); id1.setNumber("999777123"); id1.setAddress("Zhushan road"); t1 = new Teacher(); t1.setTeacherName("Jackson"); t1.setTeachYears(3); t1.setAge(28); t2 = new Teacher(); t2.setTeacherName("Rose"); t2.setTeachYears(12); t2.setAge(34); } @Test public void testUpdateBasicValues() { Cellphone cell = new Cellphone(); cell.setBrand("SamSung"); cell.setPrice(3988.12); cell.setInStock('Y'); cell.setSerial(UUID.randomUUID().toString()); assertTrue(cell.save()); assertTrue(isDataExists(getTableName(cell), cell.getId())); // reduce price, sold out. cell.setPrice(2899.88); cell.setInStock('N'); assertTrue(cell.save()); Cellphone updatedCell = getCellPhone(cell.getId()); assertEquals(2899.88, updatedCell.getPrice()); assertEquals('N', (char) updatedCell.getInStock()); } @Test public void testUpdateGenericData() { Classroom classroom = new Classroom(); classroom.setName("Classroom origin"); classroom.getNews().add("n"); classroom.getNews().add("e"); classroom.getNews().add("w"); classroom.getNumbers().add(1); classroom.getNumbers().add(2); classroom.getNumbers().add(3); classroom.save(); classroom.setName("Classroom update"); classroom.getNews().add("s"); classroom.getNumbers().clear(); classroom.save(); Classroom c = LitePal.find(Classroom.class, classroom.get_id()); assertEquals("Classroom update", c.getName()); assertEquals(4, classroom.getNews().size()); assertEquals(0, classroom.getNumbers().size()); StringBuilder builder = new StringBuilder(); for (String s : classroom.getNews()) { builder.append(s); } assertEquals("news", builder.toString()); } @Test public void testUpdateM2OAssociationsOnMSide() { init(); s1.setClassroom(c1); s2.setClassroom(c1); assertTrue(c1.save()); assertTrue(c2.save()); assertTrue(s1.save()); assertTrue(s2.save()); s1.setClassroom(c2); s2.setClassroom(c2); Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(1989, 7, 7, 0, 0, 0); s2.setBirthday(calendar.getTime()); assertTrue(s1.save()); assertTrue(s2.save()); assertEquals(c2.get_id(), getForeignKeyValue(studentTable, classroomTable, s1.getId())); assertEquals(c2.get_id(), getForeignKeyValue(studentTable, classroomTable, s2.getId())); Student student2 = LitePal.find(Student.class, s2.getId()); calendar.clear(); calendar.set(1989, 7, 7, 0, 0, 0); assertEquals(calendar.getTimeInMillis(), student2.getBirthday().getTime()); } @Test public void testUpdateM2OAssociationsOnOSide() { init(); c1.getStudentCollection().add(s1); c1.getStudentCollection().add(s2); assertTrue(c1.save()); assertTrue(c2.save()); assertTrue(s1.save()); assertTrue(s2.save()); c2.getStudentCollection().add(s1); c2.getStudentCollection().add(s2); assertTrue(c2.save()); assertEquals(c2.get_id(), getForeignKeyValue(studentTable, classroomTable, s1.getId())); assertEquals(c2.get_id(), getForeignKeyValue(studentTable, classroomTable, s2.getId())); } @Test public void testUpdateM2OAssociationsOnMSideWithNotSavedModel() { init(); s1.setClassroom(c1); s2.setClassroom(c1); assertTrue(c1.save()); assertTrue(s1.save()); assertTrue(s2.save()); s1.setClassroom(c2); s2.setClassroom(c2); assertTrue(s1.save()); assertTrue(s2.save()); assertEquals(c1.get_id(), getForeignKeyValue(studentTable, classroomTable, s1.getId())); assertEquals(c1.get_id(), getForeignKeyValue(studentTable, classroomTable, s2.getId())); } @Test public void testUpdateM2OAssociationsOnOSideWithNotSavedModel() { init(); c1.getStudentCollection().add(s1); c1.getStudentCollection().add(s2); assertTrue(c1.save()); assertTrue(c2.save()); assertTrue(s1.save()); c2.getStudentCollection().add(s1); c2.getStudentCollection().add(s2); assertTrue(c2.save()); assertEquals(c2.get_id(), getForeignKeyValue(studentTable, classroomTable, s1.getId())); } @Test public void testUpdateM2OAssociationsOnMSideWithNull() { init(); s1.setClassroom(c1); s2.setClassroom(c1); assertTrue(c1.save()); assertTrue(s1.save()); assertTrue(s2.save()); s1.setClassroom(null); s2.setClassroom(null); assertTrue(s1.save()); assertTrue(s2.save()); assertEquals(0, getForeignKeyValue(studentTable, classroomTable, s1.getId())); assertEquals(0, getForeignKeyValue(studentTable, classroomTable, s2.getId())); } @Test public void testUpdateM2OAssociationsOnOSideWithNull() { init(); s1.setClassroom(c1); s2.setClassroom(c1); assertTrue(c1.save()); assertTrue(s1.save()); assertTrue(s2.save()); c1.setStudentCollection(null); assertTrue(c1.save()); assertEquals(0, getForeignKeyValue(studentTable, classroomTable, s1.getId())); assertEquals(0, getForeignKeyValue(studentTable, classroomTable, s2.getId())); } @Test public void testUpdateM2OAssociationsOnOSideWithEmptyCollection() { init(); s1.setClassroom(c1); s2.setClassroom(c1); assertTrue(c1.save()); assertTrue(s1.save()); assertTrue(s2.save()); c1.getStudentCollection().clear(); assertTrue(c1.save()); assertEquals(0, getForeignKeyValue(studentTable, classroomTable, s1.getId())); assertEquals(0, getForeignKeyValue(studentTable, classroomTable, s2.getId())); } @Test public void testUpdateO2OAssociations() { init(); assertTrue(s3.save()); assertTrue(id1.save()); s3.setIdcard(id1); id1.setStudent(s3); assertTrue(s3.save()); assertTrue(id1.save()); assertEquals(s3.getId(), getForeignKeyValue(idcardTable, studentTable, id1.getId())); assertEquals(id1.getId(), getForeignKeyValue(studentTable, idcardTable, s3.getId())); } @Test public void testUpdateO2OAssociationsWithNull() { init(); s3.setIdcard(id1); id1.setStudent(s3); assertTrue(s3.save()); assertTrue(id1.save()); s3.setIdcard(null); id1.setStudent(null); assertTrue(s3.save()); assertTrue(id1.save()); assertEquals(0, getForeignKeyValue(idcardTable, studentTable, id1.getId())); assertEquals(0, getForeignKeyValue(studentTable, idcardTable, s3.getId())); } @Test public void testUpdateM2MAssociations() { init(); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); List teachers = new LinkedList<>(); teachers.add(t1); teachers.add(t2); s1.setTeachers(teachers); s2.setTeachers(teachers); s3.setTeachers(teachers); List students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); t1.setStudents(students); t2.setStudents(students); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); assertTrue(isIntermediateDataCorrect(getTableName(s1), getTableName(t1), s1.getId(), t1.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s2), getTableName(t1), s2.getId(), t1.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s3), getTableName(t1), s3.getId(), t1.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s1), getTableName(t2), s1.getId(), t2.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s2), getTableName(t2), s2.getId(), t2.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s3), getTableName(t2), s3.getId(), t2.getId())); } @Test public void testUpdateM2MAssociationsWithNull() { init(); List teachers = new LinkedList<>(); teachers.add(t1); teachers.add(t2); s1.setTeachers(teachers); s2.setTeachers(teachers); s3.setTeachers(teachers); List students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); t1.setStudents(students); t2.setStudents(students); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); s1.setTeachers(null); s2.setTeachers(null); s3.setTeachers(null); t1.setStudents(null); t2.setStudents(null); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); assertFalse(isIntermediateDataCorrect(getTableName(s1), getTableName(t1), s1.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s2), getTableName(t1), s2.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s3), getTableName(t1), s3.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s1), getTableName(t2), s1.getId(), t2.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s2), getTableName(t2), s2.getId(), t2.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s3), getTableName(t2), s3.getId(), t2.getId())); } @Test public void testUpdateM2MAssociationsWithRefreshedCollection() { init(); List teachers = new LinkedList<>(); teachers.add(t1); teachers.add(t2); s1.setTeachers(teachers); s2.setTeachers(teachers); s3.setTeachers(teachers); List students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); t1.setStudents(students); t2.setStudents(students); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); teachers.clear(); teachers.add(t2); students.clear(); students.add(s3); s1.setTeachers(teachers); s2.setTeachers(teachers); s3.setTeachers(teachers); t1.setStudents(students); t2.setStudents(students); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); assertFalse(isIntermediateDataCorrect(getTableName(s1), getTableName(t1), s1.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s2), getTableName(t1), s2.getId(), t1.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s3), getTableName(t1), s3.getId(), t1.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s1), getTableName(t2), s1.getId(), t2.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s2), getTableName(t2), s2.getId(), t2.getId())); assertTrue(isIntermediateDataCorrect(getTableName(s3), getTableName(t2), s3.getId(), t2.getId())); } @Test public void testUpdateM2MAssociationsWithEmptyCollection() { init(); List teachers = new LinkedList<>(); teachers.add(t1); teachers.add(t2); s1.setTeachers(teachers); s2.setTeachers(teachers); s3.setTeachers(teachers); List students = new ArrayList<>(); students.add(s1); students.add(s2); students.add(s3); t1.setStudents(students); t2.setStudents(students); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); teachers.clear(); students.clear(); s1.setTeachers(teachers); s2.setTeachers(teachers); s3.setTeachers(teachers); t1.setStudents(students); t2.setStudents(students); assertTrue(s1.save()); assertTrue(s2.save()); assertTrue(s3.save()); assertTrue(t1.save()); assertTrue(t2.save()); assertFalse(isIntermediateDataCorrect(getTableName(s1), getTableName(t1), s1.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s2), getTableName(t1), s2.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s3), getTableName(t1), s3.getId(), t1.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s1), getTableName(t2), s1.getId(), t2.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s2), getTableName(t2), s2.getId(), t2.getId())); assertFalse(isIntermediateDataCorrect(getTableName(s3), getTableName(t2), s3.getId(), t2.getId())); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/update/UpdateUsingUpdateMethodKotlinTest.kt ================================================ package com.litepaltest.test.crud.update import android.content.ContentValues import android.database.sqlite.SQLiteException import androidx.test.filters.SmallTest import com.litepaltest.model.* import com.litepaltest.test.LitePalTestCase import junit.framework.TestCase.* import org.junit.Before import org.junit.Test import org.litepal.LitePal import org.litepal.exceptions.DataSupportException import org.litepal.extension.find import org.litepal.extension.update import org.litepal.extension.updateAll import org.litepal.tablemanager.Connector import org.litepal.util.DBUtility import java.util.* @SmallTest class UpdateUsingUpdateMethodKotlinTest : LitePalTestCase() { private var teacher: Teacher? = null private var student: Student? = null private var classroom: Classroom? = null private var studentTable: String? = null @Before fun setUp() { studentTable = DBUtility.getTableNameByClassName(Student::class.java.name) init() } private fun init() { classroom = Classroom() classroom!!.name = "English room" classroom!!.news.add("hello") classroom!!.news.add("world") classroom!!.numbers.add(123) classroom!!.numbers.add(456) teacher = Teacher() teacher!!.teacherName = "Tony" teacher!!.teachYears = 3 teacher!!.age = 23 teacher!!.isSex = false student = Student() student!!.name = "Jonny" student!!.age = 13 student!!.classroom = classroom student!!.birthday = Date() student!!.teachers.add(teacher) teacher!!.students.add(student) student!!.save() teacher!!.save() classroom!!.save() } @Test fun testUpdateWithStaticUpdate() { val values = ContentValues() values.put("TEACHERNAME", "Toy") var rowsAffected = LitePal.update(values, teacher!!.id.toLong()) assertEquals(1, rowsAffected) assertEquals("Toy", getTeacher(teacher!!.id.toLong())!!.teacherName) values.clear() values.put("aGe", 15) rowsAffected = LitePal.update(values, student!!.id.toLong()) assertEquals(1, rowsAffected) assertEquals(15, getStudent(student!!.id.toLong())!!.age) } @Test fun testUpdateWithStaticUpdateButWrongClass() { val values = ContentValues() values.put("TEACHERNAME", "Toy") try { LitePal.update(values, teacher!!.id.toLong()) } catch (e: SQLiteException) { } } @Test fun testUpdateWithStaticUpdateButWrongColumn() { val values = ContentValues() values.put("TEACHERYEARS", 13) try { LitePal.update(Teacher::class.java, values, teacher!!.id.toLong()) fail("no such column: TEACHERYEARS") } catch (e: SQLiteException) { } } @Test fun testUpdateWithStaticUpdateButNotExistsRecord() { val values = ContentValues() values.put("TEACHERNAME", "Toy") val rowsAffected = LitePal.update(Teacher::class.java, values, 998909) assertEquals(0, rowsAffected) } @Test fun testUpdateWithInstanceUpdate() { val t = Teacher() t.age = 66 t.teacherName = "Jobs" t.teachYears = 33 t.isSex = false val rowsAffected = t.update(teacher!!.id.toLong()) assertEquals(1, rowsAffected) val newTeacher = getTeacher(teacher!!.id.toLong()) assertEquals("Jobs", newTeacher!!.teacherName) assertEquals(33, newTeacher.teachYears) assertEquals(66, newTeacher.age) } @Test fun testUpdateWithDefaultValueWithInstanceUpdate() { val t = Teacher() t.teacherName = "" t.teachYears = 0 t.isSex = true t.age = 22 val affectedTeacher = t.update(teacher!!.id.toLong()) assertEquals(0, affectedTeacher) val newTeacher = getTeacher(teacher!!.id.toLong()) assertEquals(teacher!!.age, newTeacher!!.age) assertEquals(teacher!!.teacherName, newTeacher.teacherName) assertEquals(teacher!!.teachYears, newTeacher.teachYears) assertEquals(teacher!!.isSex, newTeacher.isSex) val s = Student() s.name = null s.age = 0 val affectedStudent = s.update(student!!.id.toLong()) assertEquals(0, affectedStudent) val newStudent = getStudent(student!!.id.toLong()) assertEquals(student!!.name, newStudent!!.name) assertEquals(student!!.age, newStudent.age) } @Test fun testUpdateToDefaultValueWithInstanceUpdate() { val s = Student() s.setToDefault("age") s.setToDefault("name") s.setToDefault("birthday") val affectedStudent = s.update(student!!.id.toLong()) assertEquals(1, affectedStudent) val newStudent = LitePal.find(student!!.id.toLong()) assertNull(newStudent!!.birthday) assertNull(newStudent.name) assertEquals(0, newStudent.age) val t = Teacher() t.age = 45 t.teachYears = 5 t.teacherName = "John" t.setToDefault("teacherName") t.setToDefault("age") val affectedTeacher = t.update(teacher!!.id.toLong()) assertEquals(1, affectedTeacher) val newTeacher = getTeacher(teacher!!.id.toLong()) assertEquals(22, newTeacher!!.age) assertEquals("", newTeacher.teacherName) assertEquals(5, newTeacher.teachYears) } @Test fun testUpdateToDefaultValueWithInstanceUpdateButWrongField() { try { val t = Teacher() t.setToDefault("name") t.update(t.id.toLong()) fail() } catch (e: DataSupportException) { assertEquals( "The name field in com.litepaltest.model.Teacher class is necessary which does not exist.", e.message) } } @Test fun testUpdateWithInstanceUpdateWithConstructor() { try { val computer = Computer("ACER", 5444.0) computer.save() computer.update(computer.id) fail() } catch (e: DataSupportException) { assertEquals("com.litepaltest.model.Computer needs a default constructor.", e.message) } } @Test fun testUpdateWithInstanceUpdateButNotExistsRecord() { val t = Teacher() t.teacherName = "Johnny" val rowsAffected = t.update(189876465) assertEquals(0, rowsAffected) } @Test fun testUpdateAllWithStaticUpdate() { var s: Student val ids = IntArray(5) for (i in 0..4) { s = Student() s.name = "Dusting" s.age = i + 10 s.save() ids[i] = s.id } val values = ContentValues() values.put("age", 24) var affectedRows = LitePal.updateAll(values, "name = ? and age = ?", "Dusting", "13") assertEquals(1, affectedRows) val updatedStu = getStudent(ids[3].toLong()) assertEquals(24, updatedStu!!.age) values.clear() values.put("name", "Dustee") affectedRows = LitePal.updateAll(values, "name = ?", "Dusting") assertEquals(5, affectedRows) val students = getStudents(ids) for (updatedStudent in students) { assertEquals("Dustee", updatedStudent.name) } } @Test fun testUpdateAllRowsWithStaticUpdate() { var allRows = getRowsCount(studentTable) val values = ContentValues() values.put("name", "Zuckerburg") var affectedRows = LitePal.updateAll(values) assertEquals(allRows, affectedRows) val table = DBUtility.getIntermediateTableName(studentTable, DBUtility.getTableNameByClassName(Teacher::class.java.name)) allRows = getRowsCount(table) values.clear() values.putNull(studentTable!! + "_id") affectedRows = LitePal.updateAll(table, values) assertEquals(allRows, affectedRows) } @Test fun testUpdateAllWithStaticUpdateButWrongConditions() { val values = ContentValues() values.put("name", "Dustee") try { LitePal.updateAll(values, "name = 'Dustin'", "aaa") fail() } catch (e: DataSupportException) { assertEquals("The parameters in conditions are incorrect.", e.message) } try { LitePal.updateAll(values, null, null) fail() } catch (e: DataSupportException) { assertEquals("The parameters in conditions are incorrect.", e.message) } try { LitePal.updateAll(values, "address = ?", "HK") fail() } catch (e: SQLiteException) { } } @Test fun testUpdateAllWithInstanceUpdate() { var s: Student val ids = IntArray(5) for (i in 0..4) { s = Student() s.name = "Jessica" s.age = i + 10 s.save() ids[i] = s.id } val date = Date() val toUpdate = Student() toUpdate.age = 24 toUpdate.birthday = date var affectedRows = toUpdate.updateAll("name = ? and age = ?", "Jessica", "13") assertEquals(1, affectedRows) val updatedStu = LitePal.find(Student::class.java, ids[3].toLong()) assertEquals(24, updatedStu!!.age) assertEquals(date.time, updatedStu.birthday.time) toUpdate.age = 18 toUpdate.name = "Jess" affectedRows = toUpdate.updateAll("name = ?", "Jessica") assertEquals(5, affectedRows) val students = getStudents(ids) for (updatedStudent in students) { assertEquals("Jess", updatedStudent.name) assertEquals(18, updatedStudent.age) } } @Test fun testUpdateAllRowsWithInstanceUpdate() { val c = Connector.getDatabase().query(studentTable, null, null, null, null, null, null) val allRows = c.count c.close() val student = Student() student.name = "Zuckerburg" val affectedRows = student.updateAll() assertEquals(allRows, affectedRows) } @Test fun testUpdateAllWithDefaultValueWithInstanceUpdate() { var tea: Teacher? val ids = IntArray(5) for (i in 0..4) { tea = Teacher() tea.teacherName = "Rose Jackson" tea.age = 50 tea.teachYears = 15 tea.isSex = false tea.save() ids[i] = tea.id } val t = Teacher() t.teacherName = "" t.teachYears = 0 t.isSex = true t.age = 22 val affectedTeacher = t.updateAll("teachername = 'Rose Jackson'") assertEquals(0, affectedTeacher) val teachers = getTeachers(ids) for (updatedTeacher in teachers) { assertEquals("Rose Jackson", updatedTeacher.teacherName) assertEquals(50, updatedTeacher.age) assertEquals(15, updatedTeacher.teachYears) assertEquals(false, updatedTeacher.isSex) } } @Test fun testUpdateAllToDefaultValueWithInstanceUpdate() { var stu: Student val ids = IntArray(5) for (i in 0..4) { stu = Student() stu.name = "Michael Jackson" stu.age = 18 stu.save() ids[i] = stu.id } val s = Student() s.setToDefault("age") s.setToDefault("name") val affectedStudent = s.updateAll("name = 'Michael Jackson'") assertEquals(5, affectedStudent) val students = getStudents(ids) for (updatedStudent in students) { assertEquals(null, updatedStudent.name) assertEquals(0, updatedStudent.age) } } @Test fun testUpdateAllToDefaultValueWithInstanceUpdateButWrongField() { try { val t = Teacher() t.setToDefault("name") t.updateAll("") fail() } catch (e: DataSupportException) { assertEquals( "The name field in com.litepaltest.model.Teacher class is necessary which does not exist.", e.message) } } @Test fun testUpdateAllWithInstanceUpdateButWrongConditions() { val student = Student() student.name = "Dustee" try { student.updateAll("name = 'Dustin'", "aaa") fail() } catch (e: DataSupportException) { assertEquals("The parameters in conditions are incorrect.", e.message) } try { student.updateAll(null, null) fail() } catch (e: DataSupportException) { assertEquals("The parameters in conditions are incorrect.", e.message) } try { student.updateAll("address = ?", "HK") fail() } catch (e: Exception) { } } @Test fun testUpdateGenericData() { val c = Classroom() c.name = "Math room" c.news.add("news") c.news.add("paper") c.update(classroom!!._id.toLong()) var result = LitePal.find(classroom!!._id.toLong()) assertEquals("Math room", result!!.name) val builder = StringBuilder() for (s in result.news) { builder.append(s) } assertEquals("newspaper", builder.toString()) assertEquals(2, result.numbers.size) val c2 = Classroom() c2.setToDefault("numbers") c2.update(classroom!!._id.toLong()) result = LitePal.find(classroom!!._id.toLong()) assertEquals("Math room", result!!.name) assertEquals(2, result.news.size) assertEquals(0, result.numbers.size) } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/crud/update/UpdateUsingUpdateMethodTest.java ================================================ package com.litepaltest.test.crud.update; import java.util.Date; import java.util.List; import org.junit.Before; import org.junit.Test; import org.litepal.LitePal; import org.litepal.exceptions.DataSupportException; import org.litepal.tablemanager.Connector; import org.litepal.util.DBUtility; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteException; import androidx.test.filters.SmallTest; import com.litepaltest.model.Classroom; import com.litepaltest.model.Computer; import com.litepaltest.model.Student; import com.litepaltest.model.Teacher; import com.litepaltest.test.LitePalTestCase; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.fail; @SmallTest public class UpdateUsingUpdateMethodTest extends LitePalTestCase { private Teacher teacher; private Teacher t1; private Teacher t2; private Student student; private Student s1; private Student s2; private Student s3; private Classroom classroom; private Classroom c1; private Classroom c2; private String studentTable; @Before public void setUp() { studentTable = DBUtility.getTableNameByClassName(Student.class.getName()); init(); } private void init() { classroom = new Classroom(); classroom.setName("English room"); classroom.getNews().add("hello"); classroom.getNews().add("world"); classroom.getNumbers().add(123); classroom.getNumbers().add(456); teacher = new Teacher(); teacher.setTeacherName("Tony"); teacher.setTeachYears(3); teacher.setAge(23); teacher.setSex(false); student = new Student(); student.setName("Jonny"); student.setAge(13); student.setClassroom(classroom); student.setBirthday(new Date()); student.getTeachers().add(teacher); teacher.getStudents().add(student); student.save(); teacher.save(); classroom.save(); } private void initForAssociations() { c1 = new Classroom(); c1.setName("Working room"); c2 = new Classroom(); c2.setName("Resting room"); s1 = new Student(); s1.setName("Parker"); s1.setAge(18); s2 = new Student(); s2.setName("Peter"); s2.setAge(19); s3 = new Student(); s3.setName("Miley"); s3.setAge(16); t1 = new Teacher(); t1.setTeacherName("Jackson"); t1.setTeachYears(3); t1.setAge(28); t2 = new Teacher(); t2.setTeacherName("Rose"); t2.setTeachYears(12); t2.setAge(34); } @Test public void testUpdateWithStaticUpdate() { ContentValues values = new ContentValues(); values.put("TEACHERNAME", "Toy"); int rowsAffected = LitePal.update(Teacher.class, values, teacher.getId()); assertEquals(1, rowsAffected); assertEquals("Toy", getTeacher(teacher.getId()).getTeacherName()); values.clear(); values.put("aGe", 15); rowsAffected = LitePal.update(Student.class, values, student.getId()); assertEquals(1, rowsAffected); assertEquals(15, getStudent(student.getId()).getAge()); } @Test public void testUpdateWithStaticUpdateButWrongClass() { ContentValues values = new ContentValues(); values.put("TEACHERNAME", "Toy"); try { LitePal.update(Object.class, values, teacher.getId()); } catch (SQLiteException ignored) { } } @Test public void testUpdateWithStaticUpdateButWrongColumn() { ContentValues values = new ContentValues(); values.put("TEACHERYEARS", 13); try { LitePal.update(Teacher.class, values, teacher.getId()); fail("no such column: TEACHERYEARS"); } catch (SQLiteException ignored) { } } @Test public void testUpdateWithStaticUpdateButNotExistsRecord() { ContentValues values = new ContentValues(); values.put("TEACHERNAME", "Toy"); int rowsAffected = LitePal.update(Teacher.class, values, 998909); assertEquals(0, rowsAffected); } @Test public void testUpdateWithInstanceUpdate() { Teacher t = new Teacher(); t.setAge(66); t.setTeacherName("Jobs"); t.setTeachYears(33); t.setSex(false); int rowsAffected = t.update(teacher.getId()); assertEquals(1, rowsAffected); Teacher newTeacher = getTeacher(teacher.getId()); assertEquals("Jobs", newTeacher.getTeacherName()); assertEquals(33, newTeacher.getTeachYears()); assertEquals(66, newTeacher.getAge()); } @Test public void testUpdateWithDefaultValueWithInstanceUpdate() { Teacher t = new Teacher(); t.setTeacherName(""); t.setTeachYears(0); t.setSex(true); t.setAge(22); int affectedTeacher = t.update(teacher.getId()); assertEquals(0, affectedTeacher); Teacher newTeacher = getTeacher(teacher.getId()); assertEquals(teacher.getAge(), newTeacher.getAge()); assertEquals(teacher.getTeacherName(), newTeacher.getTeacherName()); assertEquals(teacher.getTeachYears(), newTeacher.getTeachYears()); assertEquals(teacher.isSex(), newTeacher.isSex()); Student s = new Student(); s.setName(null); s.setAge(0); int affectedStudent = s.update(student.getId()); assertEquals(0, affectedStudent); Student newStudent = getStudent(student.getId()); assertEquals(student.getName(), newStudent.getName()); assertEquals(student.getAge(), newStudent.getAge()); } @Test public void testUpdateToDefaultValueWithInstanceUpdate() { Student s = new Student(); s.setToDefault("age"); s.setToDefault("name"); s.setToDefault("birthday"); int affectedStudent = s.update(student.getId()); assertEquals(1, affectedStudent); Student newStudent = LitePal.find(Student.class, student.getId()); assertNull(newStudent.getBirthday()); assertNull(newStudent.getName()); assertEquals(0, newStudent.getAge()); Teacher t = new Teacher(); t.setAge(45); t.setTeachYears(5); t.setTeacherName("John"); t.setToDefault("teacherName"); t.setToDefault("age"); int affectedTeacher = t.update(teacher.getId()); assertEquals(1, affectedTeacher); Teacher newTeacher = getTeacher(teacher.getId()); assertEquals(22, newTeacher.getAge()); assertEquals("", newTeacher.getTeacherName()); assertEquals(5, newTeacher.getTeachYears()); } @Test public void testUpdateToDefaultValueWithInstanceUpdateButWrongField() { try { Teacher t = new Teacher(); t.setToDefault("name"); t.update(t.getId()); fail(); } catch (DataSupportException e) { assertEquals( "The name field in com.litepaltest.model.Teacher class is necessary which does not exist.", e.getMessage()); } } @Test public void testUpdateWithInstanceUpdateWithConstructor() { try { Computer computer = new Computer("ACER", 5444); computer.save(); computer.update(computer.getId()); fail(); } catch (DataSupportException e) { assertEquals("com.litepaltest.model.Computer needs a default constructor.", e.getMessage()); } } @Test public void testUpdateWithInstanceUpdateButNotExistsRecord() { Teacher t = new Teacher(); t.setTeacherName("Johnny"); int rowsAffected = t.update(189876465); assertEquals(0, rowsAffected); } // public void testUpdateM2OAssociationsOnMSideWithInstanceUpdate() { // initForAssociations(); // s1.setClassroom(c1); // s2.setClassroom(c1); // assertTrue(c1.save()); // assertTrue(c2.save()); // assertTrue(s1.save()); // assertTrue(s2.save()); // Student st = new Student(); // st.setClassroom(c2); // int rowsAffected = st.update(s1.getId()); // assertEquals(1, rowsAffected); // rowsAffected = st.update(s2.getId()); // assertEquals(1, rowsAffected); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s1.getId())); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s2.getId())); // } // // public void // testUpdateM2OAssociationsAndOtherFieldsOnMSideWithInstanceUpdate() { // initForAssociations(); // s1.setClassroom(c1); // s2.setClassroom(c1); // assertTrue(c1.save()); // assertTrue(c2.save()); // assertTrue(s1.save()); // assertTrue(s2.save()); // Student st = new Student(); // st.setName("Jackson"); // st.setClassroom(c2); // int rowsAffected = st.update(s1.getId()); // assertEquals(1, rowsAffected); // rowsAffected = st.update(s2.getId()); // assertEquals(1, rowsAffected); // assertEquals("Jackson", getStudent(s1.getId()).getName()); // assertEquals("Jackson", getStudent(s2.getId()).getName()); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s1.getId())); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s2.getId())); // } // // public void testUpdateM2OAssociationsOnOSideWithInstanceUpdate() { // initForAssociations(); // c1.getStudentCollection().add(s1); // c1.getStudentCollection().add(s2); // assertTrue(c1.save()); // assertTrue(c2.save()); // assertTrue(s1.save()); // assertTrue(s2.save()); // Classroom c = new Classroom(); // c.getStudentCollection().add(s1); // c.getStudentCollection().add(s2); // int rowsAffected = c.update(c2.get_id()); // assertEquals(2, rowsAffected); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s1.getId())); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s2.getId())); // } // // public void // testUpdateM2OAssociationsAndOtherFieldsOnOSideWithInstanceUpdate() { // initForAssociations(); // c1.getStudentCollection().add(s1); // c1.getStudentCollection().add(s2); // assertTrue(c1.save()); // assertTrue(c2.save()); // assertTrue(s1.save()); // assertTrue(s2.save()); // Classroom c = new Classroom(); // c.setName("Game room"); // c.getStudentCollection().add(s1); // c.getStudentCollection().add(s2); // int rowsAffected = c.update(c2.get_id()); // assertEquals(3, rowsAffected); // assertEquals("Game room", getClassroom(c2.get_id()).getName()); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s1.getId())); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s2.getId())); // } // // public void // testUpdateM2OAssociationsOnMSideWithNotExistsRecordWithInstanceUpdate() { // initForAssociations(); // s1.setClassroom(c1); // s2.setClassroom(c1); // assertTrue(c1.save()); // assertTrue(s1.save()); // assertTrue(s2.save()); // Student s = new Student(); // s.setClassroom(c2); // int rowsAffected = s.update(s1.getId()); // assertEquals(0, rowsAffected); // s.update(s2.getId()); // assertEquals(0, rowsAffected); // assertEquals(c1.get_id(), getForeignKeyValue("student", "classroom", // s1.getId())); // assertEquals(c1.get_id(), getForeignKeyValue("student", "classroom", // s2.getId())); // } // // public void // testUpdateM2OAssociationsOnOSideWithNotExistsRecordWithInstanceUpdate() { // initForAssociations(); // c1.getStudentCollection().add(s1); // c1.getStudentCollection().add(s2); // assertTrue(c1.save()); // assertTrue(c2.save()); // assertTrue(s1.save()); // Classroom c = new Classroom(); // c.getStudentCollection().add(s1); // c.getStudentCollection().add(s2); // c.update(c2.get_id()); // assertEquals(c2.get_id(), getForeignKeyValue("student", "classroom", // s1.getId())); // } // // public void testUpdateM2OAssociationsOnMSideWithNullWithInstanceUpdate() // { // initForAssociations(); // s1.setClassroom(c1); // s2.setClassroom(c1); // assertTrue(c1.save()); // assertTrue(s1.save()); // assertTrue(s2.save()); // } @Test public void testUpdateAllWithStaticUpdate() { Student s; int[] ids = new int[5]; for (int i = 0; i < 5; i++) { s = new Student(); s.setName("Dusting"); s.setAge(i + 10); s.save(); ids[i] = s.getId(); } ContentValues values = new ContentValues(); values.put("age", 24); int affectedRows = LitePal.updateAll(Student.class, values, "name = ? and age = ?", "Dusting", "13"); assertEquals(1, affectedRows); Student updatedStu = getStudent(ids[3]); assertEquals(24, updatedStu.getAge()); values.clear(); values.put("name", "Dustee"); affectedRows = LitePal.updateAll(Student.class, values, "name = ?", "Dusting"); assertEquals(5, affectedRows); List students = getStudents(ids); for (Student updatedStudent : students) { assertEquals("Dustee", updatedStudent.getName()); } } @Test public void testUpdateAllRowsWithStaticUpdate() { int allRows = getRowsCount(studentTable); ContentValues values = new ContentValues(); values.put("name", "Zuckerburg"); int affectedRows = LitePal.updateAll(Student.class, values); assertEquals(allRows, affectedRows); String table = DBUtility.getIntermediateTableName(studentTable, DBUtility.getTableNameByClassName(Teacher.class.getName())); allRows = getRowsCount(table); values.clear(); values.putNull(studentTable + "_id"); affectedRows = LitePal.updateAll(table, values); assertEquals(allRows, affectedRows); } @Test public void testUpdateAllWithStaticUpdateButWrongConditions() { ContentValues values = new ContentValues(); values.put("name", "Dustee"); try { LitePal.updateAll(Student.class, values, "name = 'Dustin'", "aaa"); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } try { LitePal.updateAll(Student.class, values, null, null); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } try { LitePal.updateAll(Student.class, values, "address = ?", "HK"); fail(); } catch (SQLiteException ignored) { } } @Test public void testUpdateAllWithInstanceUpdate() { Student s; int[] ids = new int[5]; for (int i = 0; i < 5; i++) { s = new Student(); s.setName("Jessica"); s.setAge(i + 10); s.save(); ids[i] = s.getId(); } Date date = new Date(); Student toUpdate = new Student(); toUpdate.setAge(24); toUpdate.setBirthday(date); int affectedRows = toUpdate.updateAll("name = ? and age = ?", "Jessica", "13"); assertEquals(1, affectedRows); Student updatedStu = LitePal.find(Student.class, ids[3]); assertEquals(24, updatedStu.getAge()); assertEquals(date.getTime(), updatedStu.getBirthday().getTime()); toUpdate.setAge(18); toUpdate.setName("Jess"); affectedRows = toUpdate.updateAll("name = ?", "Jessica"); assertEquals(5, affectedRows); List students = getStudents(ids); for (Student updatedStudent : students) { assertEquals("Jess", updatedStudent.getName()); assertEquals(18, updatedStudent.getAge()); } } @Test public void testUpdateAllRowsWithInstanceUpdate() { Cursor c = Connector.getDatabase().query(studentTable, null, null, null, null, null, null); int allRows = c.getCount(); c.close(); Student student = new Student(); student.setName("Zuckerburg"); int affectedRows = student.updateAll(); assertEquals(allRows, affectedRows); } @Test public void testUpdateAllWithDefaultValueWithInstanceUpdate() { Teacher tea; int[] ids = new int[5]; for (int i = 0; i < 5; i++) { tea = new Teacher(); tea.setTeacherName("Rose Jackson"); tea.setAge(50); tea.setTeachYears(15); tea.setSex(false); tea.save(); ids[i] = tea.getId(); } Teacher t = new Teacher(); t.setTeacherName(""); t.setTeachYears(0); t.setSex(true); t.setAge(22); int affectedTeacher = t.updateAll("teachername = 'Rose Jackson'"); assertEquals(0, affectedTeacher); List teachers = getTeachers(ids); for (Teacher updatedTeacher : teachers) { assertEquals("Rose Jackson", updatedTeacher.getTeacherName()); assertEquals(50, updatedTeacher.getAge()); assertEquals(15, updatedTeacher.getTeachYears()); assertFalse(updatedTeacher.isSex()); } } @Test public void testUpdateAllToDefaultValueWithInstanceUpdate() { Student stu; int[] ids = new int[5]; for (int i = 0; i < 5; i++) { stu = new Student(); stu.setName("Michael Jackson"); stu.setAge(18); stu.save(); ids[i] = stu.getId(); } Student s = new Student(); s.setToDefault("age"); s.setToDefault("name"); int affectedStudent = s.updateAll("name = 'Michael Jackson'"); assertEquals(5, affectedStudent); List students = getStudents(ids); for (Student updatedStudent : students) { assertNull(updatedStudent.getName()); assertEquals(0, updatedStudent.getAge()); } } @Test public void testUpdateAllToDefaultValueWithInstanceUpdateButWrongField() { try { Teacher t = new Teacher(); t.setToDefault("name"); t.updateAll(""); fail(); } catch (DataSupportException e) { assertEquals( "The name field in com.litepaltest.model.Teacher class is necessary which does not exist.", e.getMessage()); } } @Test public void testUpdateAllWithInstanceUpdateButWrongConditions() { Student student = new Student(); student.setName("Dustee"); try { student.updateAll("name = 'Dustin'", "aaa"); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } try { student.updateAll(null, null); fail(); } catch (DataSupportException e) { assertEquals("The parameters in conditions are incorrect.", e.getMessage()); } try { student.updateAll("address = ?", "HK"); fail(); } catch (Exception ignored) { } } @Test public void testUpdateGenericData() { Classroom c = new Classroom(); c.setName("Math room"); c.getNews().add("news"); c.getNews().add("paper"); c.update(classroom.get_id()); Classroom result = LitePal.find(Classroom.class, classroom.get_id()); assertEquals("Math room", result.getName()); StringBuilder builder = new StringBuilder(); for (String s : result.getNews()) { builder.append(s); } assertEquals("newspaper", builder.toString()); assertEquals(2, result.getNumbers().size()); Classroom c2 = new Classroom(); c2.setToDefault("numbers"); c2.update(classroom.get_id()); result = LitePal.find(Classroom.class, classroom.get_id()); assertEquals("Math room", result.getName()); assertEquals(2, result.getNews().size()); assertEquals(0, result.getNumbers().size()); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/util/BaseUtilityTest.java ================================================ package com.litepaltest.test.util; import androidx.test.filters.SmallTest; import org.junit.Test; import org.litepal.util.BaseUtility; import com.litepaltest.test.LitePalTestCase; import static junit.framework.TestCase.assertEquals; @SmallTest public class BaseUtilityTest extends LitePalTestCase{ @Test public void testCount() { String string = " This is a good one. That is a bad one. "; String markThis = "This"; String markIs = "is"; String markA = "a"; String markGood = "good"; String markOne = "one"; String markPoint = "."; String markSpace = " "; String markThat = "That"; String markBad = "bad"; String markNone = "none"; String markEmpty = ""; String markNull = null; assertEquals(1, BaseUtility.count(string, markThis)); assertEquals(3, BaseUtility.count(string, markIs)); assertEquals(4, BaseUtility.count(string, markA)); assertEquals(1, BaseUtility.count(string, markGood)); assertEquals(2, BaseUtility.count(string, markOne)); assertEquals(2, BaseUtility.count(string, markPoint)); assertEquals(11, BaseUtility.count(string, markSpace)); assertEquals(1, BaseUtility.count(string, markThat)); assertEquals(1, BaseUtility.count(string, markBad)); assertEquals(0, BaseUtility.count(string, markNone)); assertEquals(0, BaseUtility.count(string, markEmpty)); assertEquals(0, BaseUtility.count(string, markNull)); } } ================================================ FILE: sample/src/androidTest/java/com/litepaltest/test/util/DBUtilityTest.java ================================================ package com.litepaltest.test.util; import android.database.sqlite.SQLiteDatabase; import androidx.test.filters.SmallTest; import android.util.Pair; import com.litepaltest.model.Book; import com.litepaltest.model.Cellphone; import com.litepaltest.test.LitePalTestCase; import org.junit.Before; import org.junit.Test; import org.litepal.tablemanager.Connector; import org.litepal.util.DBUtility; import java.util.Set; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; @SmallTest public class DBUtilityTest extends LitePalTestCase { SQLiteDatabase db; @Before public void setUp() { db = Connector.getDatabase(); } @Test public void testFindIndexedColumns() { Pair, Set> pair = DBUtility.findIndexedColumns(DBUtility.getTableNameByClassName(Cellphone.class.getName()), db); Set indexColumns = pair.first; Set uniqueColumns = pair.second; assertEquals(1, indexColumns.size()); assertEquals(1, uniqueColumns.size()); assertTrue(indexColumns.contains("brand")); assertTrue(uniqueColumns.contains("serial")); pair = DBUtility.findIndexedColumns(DBUtility.getTableNameByClassName(Book.class.getName()), db); indexColumns = pair.first; uniqueColumns = pair.second; assertEquals(0, indexColumns.size()); assertEquals(0, uniqueColumns.size()); } } ================================================ FILE: sample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample/src/main/assets/litepal.xml ================================================ ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/MyApplication.java ================================================ package org.litepal.litepalsample; import android.app.Application; import org.litepal.LitePal; public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); LitePal.initialize(this); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/AggregateActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import org.litepal.litepalsample.R; public class AggregateActivity extends AppCompatActivity implements OnClickListener { public static void actionStart(Context context) { Intent intent = new Intent(context, AggregateActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.aggregate_layout); Button mCountSampleBtn = findViewById(R.id.count_sample_btn); Button mMaxSampleBtn = findViewById(R.id.max_sample_btn); Button mMinSampleBtn = findViewById(R.id.min_sample_btn); Button mAverageSampleBtn = findViewById(R.id.average_sample_btn); Button mSumSampleBtn = findViewById(R.id.sum_sample_btn); mCountSampleBtn.setOnClickListener(this); mMaxSampleBtn.setOnClickListener(this); mMinSampleBtn.setOnClickListener(this); mAverageSampleBtn.setOnClickListener(this); mSumSampleBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.count_sample_btn: CountSampleActivity.actionStart(this); break; case R.id.max_sample_btn: MaxSampleActivity.actionStart(this); break; case R.id.min_sample_btn: MinSampleActivity.actionStart(this); break; case R.id.average_sample_btn: AverageSampleActivity.actionStart(this); break; case R.id.sum_sample_btn: SumSampleActivity.actionStart(this); break; default: break; } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/AverageSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.litepal.LitePal; import org.litepal.litepalsample.R; import org.litepal.litepalsample.model.Singer; public class AverageSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mAgeEdit; private TextView mResultText; public static void actionStart(Context context) { Intent intent = new Intent(context, AverageSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.average_sample_layout); Button mAvgBtn1 = findViewById(R.id.avg_btn1); Button mAvgBtn2 = findViewById(R.id.avg_btn2); mAgeEdit = findViewById(R.id.age_edit); mResultText = findViewById(R.id.result_text); mAvgBtn1.setOnClickListener(this); mAvgBtn2.setOnClickListener(this); } @Override public void onClick(View view) { double result = 0; switch (view.getId()) { case R.id.avg_btn1: result = LitePal.average(Singer.class, "age"); mResultText.setText(String.valueOf(result)); break; case R.id.avg_btn2: try { result = LitePal.where("age > ?", mAgeEdit.getText().toString()).average( Singer.class, "age"); mResultText.setText(String.valueOf(result)); } catch (Exception e) { e.printStackTrace(); } break; default: } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/CRUDActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import org.litepal.litepalsample.R; public class CRUDActivity extends AppCompatActivity implements OnClickListener { public static void actionStart(Context context) { Intent intent = new Intent(context, CRUDActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crud_layout); Button mSaveSampleBtn = findViewById(R.id.save_sample_btn); Button mUpdateSampleBtn = findViewById(R.id.update_sample_btn); Button mDeleteSampleBtn = findViewById(R.id.delete_sample_btn); Button mQuerySampleBtn = findViewById(R.id.query_sample_btn); mSaveSampleBtn.setOnClickListener(this); mUpdateSampleBtn.setOnClickListener(this); mDeleteSampleBtn.setOnClickListener(this); mQuerySampleBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.save_sample_btn: SaveSampleActivity.actionStart(this); break; case R.id.update_sample_btn: UpdateSampleActivity.actionStart(this); break; case R.id.delete_sample_btn: DeleteSampleActivity.actionStart(this); break; case R.id.query_sample_btn: QuerySampleActivity.actionStart(this); break; default: break; } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/CountSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.litepal.LitePal; import org.litepal.litepalsample.R; import org.litepal.litepalsample.model.Singer; public class CountSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mAgeEdit; private TextView mResultText; public static void actionStart(Context context) { Intent intent = new Intent(context, CountSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.count_sample_layout); Button mCountBtn1 = findViewById(R.id.count_btn1); Button mCountBtn2 = findViewById(R.id.count_btn2); mAgeEdit = findViewById(R.id.age_edit); mResultText = findViewById(R.id.result_text); mCountBtn1.setOnClickListener(this); mCountBtn2.setOnClickListener(this); } @Override public void onClick(View view) { int result = 0; switch (view.getId()) { case R.id.count_btn1: result = LitePal.count(Singer.class); mResultText.setText(String.valueOf(result)); break; case R.id.count_btn2: try { result = LitePal.where("age > ?", mAgeEdit.getText().toString()).count( Singer.class); mResultText.setText(String.valueOf(result)); } catch (Exception e) { e.printStackTrace(); } break; default: } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/DeleteSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Toast; import org.litepal.LitePal; import org.litepal.litepalsample.R; import org.litepal.litepalsample.adapter.DataArrayAdapter; import org.litepal.litepalsample.model.Singer; import org.litepal.tablemanager.Connector; import java.util.ArrayList; import java.util.List; public class DeleteSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mSingerIdEdit; private EditText mNameToDeleteEdit; private EditText mAgeToDeleteEdit; private ProgressBar mProgressBar; private DataArrayAdapter mAdapter; private List> mList = new ArrayList<>(); public static void actionStart(Context context) { Intent intent = new Intent(context, DeleteSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.delete_sample_layout); mProgressBar = findViewById(R.id.progress_bar); mSingerIdEdit = findViewById(R.id.singer_id_edit); mNameToDeleteEdit = findViewById(R.id.name_to_delete); mAgeToDeleteEdit = findViewById(R.id.age_to_delete); Button mDeleteBtn1 = findViewById(R.id.delete_btn1); Button mDeleteBtn2 = findViewById(R.id.delete_btn2); ListView mDataListView = findViewById(R.id.data_list_view); mDeleteBtn1.setOnClickListener(this); mDeleteBtn2.setOnClickListener(this); mAdapter = new DataArrayAdapter(this, 0, mList); mDataListView.setAdapter(mAdapter); populateDataFromDB(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.delete_btn1: try { int rowsAffected = LitePal.delete(Singer.class, Long.parseLong(mSingerIdEdit.getText().toString())); Toast.makeText( this, String.format(getString(R.string.number_of_rows_affected), String.valueOf(rowsAffected)), Toast.LENGTH_SHORT).show(); populateDataFromDB(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, getString(R.string.error_param_is_not_valid), Toast.LENGTH_SHORT).show(); } break; case R.id.delete_btn2: try { int rowsAffected = LitePal.deleteAll(Singer.class, "name=? and age=?", mNameToDeleteEdit.getText().toString(), mAgeToDeleteEdit.getText() .toString()); Toast.makeText( this, String.format(getString(R.string.number_of_rows_affected), String.valueOf(rowsAffected)), Toast.LENGTH_SHORT).show(); populateDataFromDB(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, getString(R.string.error_param_is_not_valid), Toast.LENGTH_SHORT).show(); } break; default: break; } } private void populateDataFromDB() { mProgressBar.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { mList.clear(); List columnList = new ArrayList(); columnList.add("id"); columnList.add("name"); columnList.add("age"); columnList.add("ismale"); mList.add(columnList); Cursor cursor = null; try { cursor = Connector.getDatabase().rawQuery("select * from singer order by id", null); if (cursor.moveToFirst()) { do { long id = cursor.getLong(cursor.getColumnIndex("id")); String name = cursor.getString(cursor.getColumnIndex("name")); int age = cursor.getInt(cursor.getColumnIndex("age")); int isMale = cursor.getInt(cursor.getColumnIndex("ismale")); List stringList = new ArrayList(); stringList.add(String.valueOf(id)); stringList.add(name); stringList.add(String.valueOf(age)); stringList.add(String.valueOf(isMale)); mList.add(stringList); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } runOnUiThread(new Runnable() { @Override public void run() { mProgressBar.setVisibility(View.GONE); mAdapter.notifyDataSetChanged(); } }); } } }).start(); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/MainActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import org.litepal.litepalsample.R; public class MainActivity extends AppCompatActivity implements OnClickListener { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); Button mManageTableBtn = findViewById(R.id.manage_table_btn); Button mCrudBtn = findViewById(R.id.crud_btn); Button mAggregateBtn = findViewById(R.id.aggregate_btn); mManageTableBtn.setOnClickListener(this); mCrudBtn.setOnClickListener(this); mAggregateBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.manage_table_btn: ManageTablesActivity.actionStart(this); break; case R.id.crud_btn: CRUDActivity.actionStart(this); break; case R.id.aggregate_btn: AggregateActivity.actionStart(this); break; default: break; } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/ManageTablesActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import org.litepal.litepalsample.R; public class ManageTablesActivity extends AppCompatActivity implements OnClickListener { public static void actionStart(Context context) { Intent intent = new Intent(context, ManageTablesActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.manage_tables_layout); Button mCurrentModelStructureBtn = findViewById(R.id.current_model_structure_btn); Button mOperateDatabaseBtn = findViewById(R.id.operate_database_btn); mCurrentModelStructureBtn.setOnClickListener(this); mOperateDatabaseBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.current_model_structure_btn: ModelListActivity.actionStart(this); break; case R.id.operate_database_btn: TableListActivity.actionStart(this); break; default: break; } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/MaxSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.litepal.LitePal; import org.litepal.litepalsample.R; import org.litepal.litepalsample.model.Singer; public class MaxSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mAgeEdit; private TextView mResultText; public static void actionStart(Context context) { Intent intent = new Intent(context, MaxSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.max_sample_layout); Button mMaxBtn1 = findViewById(R.id.max_btn1); Button mMaxBtn2 = findViewById(R.id.max_btn2); mAgeEdit = findViewById(R.id.age_edit); mResultText = findViewById(R.id.result_text); mMaxBtn1.setOnClickListener(this); mMaxBtn2.setOnClickListener(this); } @Override public void onClick(View view) { int result = 0; switch (view.getId()) { case R.id.max_btn1: result = LitePal.max(Singer.class, "age", Integer.TYPE); mResultText.setText(String.valueOf(result)); break; case R.id.max_btn2: try { result = LitePal.where("age < ?", mAgeEdit.getText().toString()).max( Singer.class, "age", Integer.TYPE); mResultText.setText(String.valueOf(result)); } catch (Exception e) { e.printStackTrace(); } break; default: } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/MinSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.litepal.LitePal; import org.litepal.litepalsample.R; import org.litepal.litepalsample.model.Singer; public class MinSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mAgeEdit; private TextView mResultText; public static void actionStart(Context context) { Intent intent = new Intent(context, MinSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.min_sample_layout); Button mMinBtn1 = findViewById(R.id.min_btn1); Button mMinBtn2 = findViewById(R.id.min_btn2); mAgeEdit = findViewById(R.id.age_edit); mResultText = findViewById(R.id.result_text); mMinBtn1.setOnClickListener(this); mMinBtn2.setOnClickListener(this); } @Override public void onClick(View view) { int result = 0; switch (view.getId()) { case R.id.min_btn1: result = LitePal.min(Singer.class, "age", Integer.TYPE); mResultText.setText(String.valueOf(result)); break; case R.id.min_btn2: try { result = LitePal.where("age > ?", mAgeEdit.getText().toString()).min( Singer.class, "age", Integer.TYPE); mResultText.setText(String.valueOf(result)); } catch (Exception e) { e.printStackTrace(); } break; default: } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/ModelListActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import org.litepal.LitePalApplication; import org.litepal.exceptions.ParseConfigurationFileException; import org.litepal.litepalsample.R; import org.litepal.litepalsample.adapter.StringArrayAdapter; import org.litepal.util.Const; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; public class ModelListActivity extends AppCompatActivity { private List mList = new ArrayList<>(); public static void actionStart(Context context) { Intent intent = new Intent(context, ModelListActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.model_list_layout); ListView mModelListView = findViewById(R.id.model_listview); populateMappingClasses(); StringArrayAdapter mAdapter = new StringArrayAdapter(this, 0, mList); mModelListView.setAdapter(mAdapter); mModelListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView arg0, View view, int index, long id) { ModelStructureActivity.actionStart(ModelListActivity.this, mList.get(index)); } }); } private void populateMappingClasses() { try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = factory.newPullParser(); xmlPullParser.setInput(getInputStream(), "UTF-8"); int eventType = xmlPullParser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { String nodeName = xmlPullParser.getName(); switch (eventType) { case XmlPullParser.START_TAG: { if ("mapping".equals(nodeName)) { String className = xmlPullParser.getAttributeValue("", "class"); mList.add(className); } break; } default: break; } eventType = xmlPullParser.next(); } } catch (XmlPullParserException e) { throw new ParseConfigurationFileException( ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT); } catch (IOException e) { throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION); } } private InputStream getInputStream() throws IOException { AssetManager assetManager = LitePalApplication.getContext().getAssets(); String[] fileNames = assetManager.list(""); if (fileNames != null && fileNames.length > 0) { for (String fileName : fileNames) { if (Const.Config.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) { return assetManager.open(fileName, AssetManager.ACCESS_BUFFER); } } } throw new ParseConfigurationFileException( ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/ModelStructureActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import org.litepal.litepalsample.R; import org.litepal.util.BaseUtility; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; public class ModelStructureActivity extends AppCompatActivity { public static final String CLASS_NAME = "class_name"; private String mClassName; private List mList = new ArrayList<>(); public static void actionStart(Context context, String className) { Intent intent = new Intent(context, ModelStructureActivity.class); intent.putExtra(CLASS_NAME, className); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.model_structure_layout); mClassName = getIntent().getStringExtra(CLASS_NAME); ListView mModelStructureListView = findViewById(R.id.model_structure_listview); analyzeModelStructure(); ArrayAdapter mAdapter = new MyArrayAdapter(this, 0, mList); mModelStructureListView.setAdapter(mAdapter); } private void analyzeModelStructure() { Class dynamicClass = null; try { dynamicClass = Class.forName(mClassName); } catch (ClassNotFoundException e) { e.printStackTrace(); } Field[] fields = dynamicClass.getDeclaredFields(); for (Field field : fields) { int modifiers = field.getModifiers(); if (Modifier.isPrivate(modifiers) && !Modifier.isStatic(modifiers)) { Class fieldTypeClass = field.getType(); String fieldType = fieldTypeClass.getName(); if (BaseUtility.isFieldTypeSupported(fieldType)) { mList.add(field); } } } } class MyArrayAdapter extends ArrayAdapter { public MyArrayAdapter(Context context, int textViewResourceId, List objects) { super(context, textViewResourceId, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; Field field = getItem(position); if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.model_structure_item, null); } else { view = convertView; } TextView text1 = view.findViewById(R.id.text_1); text1.setText(field.getName()); TextView text2 = view.findViewById(R.id.text_2); text2.setText(field.getType().getName()); return view; } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/QuerySampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import org.litepal.litepalsample.R; public class QuerySampleActivity extends AppCompatActivity { public static void actionStart(Context context) { Intent intent = new Intent(context, QuerySampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.query_sample_layout); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/SaveSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Toast; import org.litepal.litepalsample.R; import org.litepal.litepalsample.adapter.DataArrayAdapter; import org.litepal.litepalsample.model.Singer; import org.litepal.tablemanager.Connector; import java.util.ArrayList; import java.util.List; public class SaveSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mSingerNameEdit; private EditText mSingerAgeEdit; private EditText mSingerGenderEdit; private ProgressBar mProgressBar; private ListView mDataListView; private DataArrayAdapter mAdapter; private List> mList = new ArrayList<>(); public static void actionStart(Context context) { Intent intent = new Intent(context, SaveSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.save_sample_layout); mProgressBar = findViewById(R.id.progress_bar); mSingerNameEdit = findViewById(R.id.singer_name_edit); mSingerAgeEdit = findViewById(R.id.singer_age_edit); mSingerGenderEdit = findViewById(R.id.singer_gender_edit); Button mSaveBtn = findViewById(R.id.save_btn); mDataListView = findViewById(R.id.data_list_view); mSaveBtn.setOnClickListener(this); mAdapter = new DataArrayAdapter(this, 0, mList); mDataListView.setAdapter(mAdapter); populateDataFromDB(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.save_btn: try { Singer singer = new Singer(); singer.setName(mSingerNameEdit.getText().toString()); singer.setAge(Integer.parseInt(mSingerAgeEdit.getText().toString())); singer.setMale(Boolean.parseBoolean(mSingerGenderEdit.getText().toString())); singer.save(); refreshListView(singer.getId(), singer.getName(), singer.getAge(), singer.isMale() ? 1 : 0); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, getString(R.string.error_param_is_not_valid), Toast.LENGTH_SHORT).show(); } break; default: break; } } private void populateDataFromDB() { mProgressBar.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { mList.clear(); List columnList = new ArrayList(); columnList.add("id"); columnList.add("name"); columnList.add("age"); columnList.add("ismale"); mList.add(columnList); Cursor cursor = null; try { cursor = Connector.getDatabase().rawQuery("select * from singer order by id", null); if (cursor.moveToFirst()) { do { long id = cursor.getLong(cursor.getColumnIndex("id")); String name = cursor.getString(cursor.getColumnIndex("name")); int age = cursor.getInt(cursor.getColumnIndex("age")); int isMale = cursor.getInt(cursor.getColumnIndex("ismale")); List stringList = new ArrayList(); stringList.add(String.valueOf(id)); stringList.add(name); stringList.add(String.valueOf(age)); stringList.add(String.valueOf(isMale)); mList.add(stringList); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } runOnUiThread(new Runnable() { @Override public void run() { mProgressBar.setVisibility(View.GONE); mAdapter.notifyDataSetChanged(); } }); } } }).start(); } private void refreshListView(long id, String name, int age, int isMale) { List stringList = new ArrayList(); stringList.add(String.valueOf(id)); stringList.add(name); stringList.add(String.valueOf(age)); stringList.add(String.valueOf(isMale)); mList.add(stringList); mAdapter.notifyDataSetChanged(); mDataListView.setSelection(mList.size()); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/SumSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.litepal.LitePal; import org.litepal.litepalsample.R; import org.litepal.litepalsample.model.Singer; public class SumSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mAgeEdit; private TextView mResultText; public static void actionStart(Context context) { Intent intent = new Intent(context, SumSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sum_sample_layout); Button mSumBtn1 = findViewById(R.id.sum_btn1); Button mSumBtn2 = findViewById(R.id.sum_btn2); mAgeEdit = findViewById(R.id.age_edit); mResultText = findViewById(R.id.result_text); mSumBtn1.setOnClickListener(this); mSumBtn2.setOnClickListener(this); } @Override public void onClick(View view) { int result = 0; switch (view.getId()) { case R.id.sum_btn1: result = LitePal.sum(Singer.class, "age", Integer.TYPE); mResultText.setText(String.valueOf(result)); break; case R.id.sum_btn2: try { result = LitePal.where("age > ?", mAgeEdit.getText().toString()).sum( Singer.class, "age", Integer.TYPE); mResultText.setText(String.valueOf(result)); } catch (Exception e) { e.printStackTrace(); } break; default: } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/TableListActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.ProgressBar; import org.litepal.litepalsample.R; import org.litepal.litepalsample.adapter.StringArrayAdapter; import org.litepal.tablemanager.Connector; import org.litepal.util.DBUtility; import java.util.ArrayList; import java.util.List; public class TableListActivity extends AppCompatActivity { private ProgressBar mProgressBar; private StringArrayAdapter mAdapter; private List mList = new ArrayList<>(); public static void actionStart(Context context) { Intent intent = new Intent(context, TableListActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.table_list_layout); mProgressBar = findViewById(R.id.progress_bar); ListView mTableListView = findViewById(R.id.table_listview); mAdapter = new StringArrayAdapter(this, 0, mList); mTableListView.setAdapter(mAdapter); populateTables(); mTableListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView arg0, View view, int index, long id) { TableStructureActivity.actionStart(TableListActivity.this, mList.get(index)); } }); } private void populateTables() { mProgressBar.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { List tables = DBUtility.findAllTableNames(Connector.getDatabase()); for (String table : tables) { if (table.equalsIgnoreCase("android_metadata") || table.equalsIgnoreCase("sqlite_sequence") || table.equalsIgnoreCase("table_schema")) { continue; } mList.add(table); } runOnUiThread(new Runnable() { @Override public void run() { mProgressBar.setVisibility(View.GONE); mAdapter.notifyDataSetChanged(); } }); } }).start(); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/TableStructureActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import org.litepal.litepalsample.R; import org.litepal.tablemanager.Connector; import org.litepal.tablemanager.model.ColumnModel; import org.litepal.tablemanager.model.TableModel; import org.litepal.util.DBUtility; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class TableStructureActivity extends AppCompatActivity { public static final String TABLE_NAME = "table_name"; private String mTableName; private List mList = new ArrayList<>(); public static void actionStart(Context context, String tableName) { Intent intent = new Intent(context, TableStructureActivity.class); intent.putExtra(TABLE_NAME, tableName); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.table_structure_layout); mTableName = getIntent().getStringExtra(TABLE_NAME); ListView mTableStructureListView = findViewById(R.id.table_structure_listview); analyzeTableStructure(); ArrayAdapter mAdapter = new MyArrayAdapter(this, 0, mList); mTableStructureListView.setAdapter(mAdapter); } private void analyzeTableStructure() { TableModel tableMode = DBUtility.findPragmaTableInfo(mTableName, Connector.getDatabase()); Collection columnModelList = tableMode.getColumnModels(); mList.addAll(columnModelList); } class MyArrayAdapter extends ArrayAdapter { public MyArrayAdapter(Context context, int textViewResourceId, List objects) { super(context, textViewResourceId, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; ColumnModel columnModel = getItem(position); String columnName = columnModel.getColumnName(); String columnType = columnModel.getColumnType(); boolean nullable = columnModel.isNullable(); boolean unique = columnModel.isUnique(); boolean hasIndex = columnModel.hasIndex(); String defaultValue = columnModel.getDefaultValue(); if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.table_structure_item, null); } else { view = convertView; } TextView text1 = view.findViewById(R.id.text_1); text1.setText(columnName); TextView text2 = view.findViewById(R.id.text_2); text2.setText(columnType); TextView text3 = view.findViewById(R.id.text_3); text3.setText(String.valueOf(nullable)); TextView text4 = view.findViewById(R.id.text_4); text4.setText(String.valueOf(unique)); TextView text5 = view.findViewById(R.id.text_5); text5.setText(defaultValue); TextView text6 = view.findViewById(R.id.text_6); text6.setText(String.valueOf(hasIndex)); return view; } } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/activity/UpdateSampleActivity.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Toast; import org.litepal.litepalsample.R; import org.litepal.litepalsample.adapter.DataArrayAdapter; import org.litepal.litepalsample.model.Singer; import org.litepal.tablemanager.Connector; import java.util.ArrayList; import java.util.List; public class UpdateSampleActivity extends AppCompatActivity implements OnClickListener { private EditText mSingerIdEdit; private EditText mSingerNameEdit; private EditText mSingerAgeEdit; private EditText mNameToUpdateEdit; private EditText mAgeToUpdateEdit; private ProgressBar mProgressBar; private DataArrayAdapter mAdapter; private List> mList = new ArrayList<>(); public static void actionStart(Context context) { Intent intent = new Intent(context, UpdateSampleActivity.class); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.update_sample_layout); mProgressBar = findViewById(R.id.progress_bar); mSingerIdEdit = findViewById(R.id.singer_id_edit); mSingerNameEdit = findViewById(R.id.singer_name_edit); mSingerAgeEdit = findViewById(R.id.singer_age_edit); mNameToUpdateEdit = findViewById(R.id.name_to_update); mAgeToUpdateEdit = findViewById(R.id.age_to_update); Button mUpdateBtn1 = findViewById(R.id.update_btn1); Button mUpdateBtn2 = findViewById(R.id.update_btn2); ListView mDataListView = findViewById(R.id.data_list_view); mUpdateBtn1.setOnClickListener(this); mUpdateBtn2.setOnClickListener(this); mAdapter = new DataArrayAdapter(this, 0, mList); mDataListView.setAdapter(mAdapter); populateDataFromDB(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.update_btn1: try { Singer singer = new Singer(); singer.setName(mSingerNameEdit.getText().toString()); singer.setAge(Integer.parseInt(mSingerAgeEdit.getText().toString())); int rowsAffected = singer .update(Long.parseLong(mSingerIdEdit.getText().toString())); Toast.makeText( this, String.format(getString(R.string.number_of_rows_affected), String.valueOf(rowsAffected)), Toast.LENGTH_SHORT).show(); populateDataFromDB(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, getString(R.string.error_param_is_not_valid), Toast.LENGTH_SHORT).show(); } break; case R.id.update_btn2: try { Singer singer = new Singer(); singer.setName(mSingerNameEdit.getText().toString()); singer.setAge(Integer.parseInt(mSingerAgeEdit.getText().toString())); int rowsAffected = singer.updateAll("name=? and age=?", mNameToUpdateEdit.getText() .toString(), mAgeToUpdateEdit.getText().toString()); Toast.makeText( this, String.format(getString(R.string.number_of_rows_affected), String.valueOf(rowsAffected)), Toast.LENGTH_SHORT).show(); populateDataFromDB(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, getString(R.string.error_param_is_not_valid), Toast.LENGTH_SHORT).show(); } break; default: break; } } private void populateDataFromDB() { mProgressBar.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { mList.clear(); List columnList = new ArrayList<>(); columnList.add("id"); columnList.add("name"); columnList.add("age"); columnList.add("ismale"); mList.add(columnList); Cursor cursor = null; try { cursor = Connector.getDatabase().rawQuery("select * from singer order by id", null); if (cursor.moveToFirst()) { do { long id = cursor.getLong(cursor.getColumnIndex("id")); String name = cursor.getString(cursor.getColumnIndex("name")); int age = cursor.getInt(cursor.getColumnIndex("age")); int isMale = cursor.getInt(cursor.getColumnIndex("ismale")); List stringList = new ArrayList<>(); stringList.add(String.valueOf(id)); stringList.add(name); stringList.add(String.valueOf(age)); stringList.add(String.valueOf(isMale)); mList.add(stringList); } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } runOnUiThread(new Runnable() { @Override public void run() { mProgressBar.setVisibility(View.GONE); mAdapter.notifyDataSetChanged(); } }); } } }).start(); } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/adapter/DataArrayAdapter.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.adapter; import java.util.List; import org.litepal.litepalsample.util.Utility; import android.content.Context; import android.text.TextUtils.TruncateAt; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.TextView; public class DataArrayAdapter extends ArrayAdapter> { public DataArrayAdapter(Context context, int textViewResourceId, List> objects) { super(context, textViewResourceId, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { List dataList = getItem(position); LinearLayout layout; if (convertView == null) { layout = new LinearLayout(getContext()); } else { layout = (LinearLayout) convertView; } layout.removeAllViews(); int width = Utility.dp2px(getContext(), 100); int height = Utility.dp2px(getContext(), 30); for (String data : dataList) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, height); TextView textView = new TextView(getContext()); textView.setText(data); textView.setSingleLine(true); textView.setEllipsize(TruncateAt.END); textView.setGravity(Gravity.CENTER_VERTICAL); layout.addView(textView, params); } return layout; } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/adapter/StringArrayAdapter.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.adapter; import java.util.List; import org.litepal.litepalsample.R; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; public class StringArrayAdapter extends ArrayAdapter { public StringArrayAdapter(Context context, int textViewResourceId, List objects) { super(context, textViewResourceId, objects); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.simple_list_item, null); } else { view = convertView; } TextView textView = (TextView) view.findViewById(R.id.text_1); textView.setText(getItem(position)); return view; } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/model/Album.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.model; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.litepal.crud.LitePalSupport; public class Album extends LitePalSupport { private long id; // @Column(ignore = false, unique = false, nullable = false, defaultValue = "888") private int sales; // @Column(nullable = false) private String name; // @Column(ignore = false, nullable = false) private String publisher; // @Column(nullable = false, ignore = false) private double price; // @Column(unique = true, ignore = false) private String serial; // @Column(ignore = false, nullable = false, defaultValue = "100") private Date release; private Singer singer; private List songs = new ArrayList(); public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPublisher() { return publisher; } public void setPublisher(String publisher) { this.publisher = publisher; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Date getRelease() { return release; } public void setRelease(Date release) { this.release = release; } public Singer getSinger() { return singer; } public void setSinger(Singer singer) { this.singer = singer; } public List getSongs() { return songs; } public void setSongs(List songs) { this.songs = songs; } public String getSerial() { return serial; } public void setSerial(String serial) { this.serial = serial; } public int getSales() { return sales; } public void setSales(int sales) { this.sales = sales; } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/model/Singer.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.model; import java.util.ArrayList; import java.util.List; import org.litepal.crud.LitePalSupport; public class Singer extends LitePalSupport { private long id; private String name; private int age; private boolean isMale; private List albums = new ArrayList(); public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public boolean isMale() { return isMale; } public void setMale(boolean isMale) { this.isMale = isMale; } public List getAlbums() { return albums; } public void setAlbums(List albums) { this.albums = albums; } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/model/Song.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.model; import org.litepal.annotation.Column; import org.litepal.crud.LitePalSupport; public class Song extends LitePalSupport { private long id; @Column(index = true) private String name; @Column(unique = true, index = true) private String lyric; private String duration; private Album album; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLyric() { return lyric; } public void setLyric(String lyric) { this.lyric = lyric; } public String getDuration() { return duration; } public void setDuration(String duration) { this.duration = duration; } public Album getAlbum() { return album; } public void setAlbum(Album album) { this.album = album; } } ================================================ FILE: sample/src/main/java/org/litepal/litepalsample/util/Utility.java ================================================ /* * Copyright (C) Tony Green, Litepal Framework Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.litepal.litepalsample.util; import android.content.Context; public class Utility { public static int dp2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } public static int px2dp(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } } ================================================ FILE: sample/src/main/res/layout/aggregate_layout.xml ================================================