├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── runConfigurations.xml ├── compiler.xml ├── gradle.xml └── misc.xml ├── app ├── .gitignore ├── .DS_Store ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── hpsc.jpg │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ └── dimens.xml │ │ │ ├── xml │ │ │ │ ├── authenticator.xml │ │ │ │ └── syncadapter.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── me │ │ │ │ └── himanshusoni │ │ │ │ └── quantumflux │ │ │ │ └── sample │ │ │ │ ├── sync │ │ │ │ ├── SyncConstants.java │ │ │ │ ├── AuthenticatorService.java │ │ │ │ ├── SyncService.java │ │ │ │ ├── SyncAdapter.java │ │ │ │ ├── Authenticator.java │ │ │ │ └── SyncUtils.java │ │ │ │ ├── data │ │ │ │ ├── Author.java │ │ │ │ ├── Publisher.java │ │ │ │ ├── Book.java │ │ │ │ ├── view │ │ │ │ │ └── BookAuthor.java │ │ │ │ └── type │ │ │ │ │ └── BlobTypeMapping.java │ │ │ │ ├── SampleApplication.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── me │ │ └── himanshusoni │ │ └── quantumflux │ │ └── sample │ │ └── ApplicationTest.java ├── proguard-rules.pro ├── build.gradle └── app.iml ├── quantum-flux ├── .gitignore ├── .DS_Store ├── src │ ├── .DS_Store │ ├── main │ │ ├── .DS_Store │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── java │ │ │ └── me │ │ │ │ └── himanshusoni │ │ │ │ └── quantumflux │ │ │ │ ├── model │ │ │ │ ├── annotation │ │ │ │ │ ├── Authority.java │ │ │ │ │ ├── References.java │ │ │ │ │ ├── ChangeListeners.java │ │ │ │ │ ├── Column │ │ │ │ │ │ ├── Unique.java │ │ │ │ │ │ ├── PrimaryKey.java │ │ │ │ │ │ └── Column.java │ │ │ │ │ ├── Indices.java │ │ │ │ │ ├── Index.java │ │ │ │ │ ├── Table.java │ │ │ │ │ └── TableConstraint.java │ │ │ │ ├── util │ │ │ │ │ ├── QuantumFluxException.java │ │ │ │ │ ├── NamingUtils.java │ │ │ │ │ ├── QuantumFluxCursorFactory.java │ │ │ │ │ ├── ContentResolverValues.java │ │ │ │ │ ├── ReferenceMap.java │ │ │ │ │ ├── CursorIterator.java │ │ │ │ │ ├── TableDetailsCache.java │ │ │ │ │ ├── QuantumFluxBatchDispatcher.java │ │ │ │ │ ├── QuantumFluxCursor.java │ │ │ │ │ ├── ManifestHelper.java │ │ │ │ │ └── ModelInflater.java │ │ │ │ ├── generate │ │ │ │ │ ├── TableView.java │ │ │ │ │ ├── TableViewGenerator.java │ │ │ │ │ ├── TableGenerator.java │ │ │ │ │ └── TableDetails.java │ │ │ │ ├── map │ │ │ │ │ ├── types │ │ │ │ │ │ ├── LongType.java │ │ │ │ │ │ ├── FloatType.java │ │ │ │ │ │ ├── ShortType.java │ │ │ │ │ │ ├── DoubleType.java │ │ │ │ │ │ ├── IntegerType.java │ │ │ │ │ │ ├── StringType.java │ │ │ │ │ │ ├── DateType.java │ │ │ │ │ │ ├── UUIDType.java │ │ │ │ │ │ ├── BooleanType.java │ │ │ │ │ │ ├── BigDecimalType.java │ │ │ │ │ │ └── CalendarType.java │ │ │ │ │ ├── SqlColumnMapping.java │ │ │ │ │ └── SqlColumnMappingFactory.java │ │ │ │ ├── query │ │ │ │ │ ├── DataFilterClause.java │ │ │ │ │ ├── DataGroupClause.java │ │ │ │ │ ├── SQLSegment.java │ │ │ │ │ ├── QueryBuilder.java │ │ │ │ │ ├── DataFilterCriteria.java │ │ │ │ │ └── DataFilterCriterion.java │ │ │ │ ├── loader │ │ │ │ │ ├── QuantumFluxLoaderCallback.java │ │ │ │ │ ├── support │ │ │ │ │ │ ├── QuantumFluxLoaderCallback.java │ │ │ │ │ │ ├── QuantumFluxCursorAdaptor.java │ │ │ │ │ │ ├── QuantumFluxLoader.java │ │ │ │ │ │ └── QuantumFluxAsyncCursorAdaptor.java │ │ │ │ │ ├── QuantumFluxCursorAdaptor.java │ │ │ │ │ ├── QuantumFluxLoader.java │ │ │ │ │ └── QuantumFluxAsyncCursorAdaptor.java │ │ │ │ ├── QuantumFluxBaseRecord.java │ │ │ │ └── QuantumFluxRecord.java │ │ │ │ ├── QuantumFluxDatabaseUpgradeListener.java │ │ │ │ ├── logger │ │ │ │ └── QuantumFluxLog.java │ │ │ │ ├── QuantumFluxDatabase.java │ │ │ │ ├── provider │ │ │ │ └── util │ │ │ │ │ └── UriMatcherHelper.java │ │ │ │ └── QuantumFluxSyncHelper.java │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── me │ │ └── himanshusoni │ │ └── quantumflux │ │ └── ApplicationTest.java ├── proguard-rules.pro ├── build.gradle └── quantum-flux.iml ├── settings.gradle ├── .DS_Store ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── QuantumFlux.iml ├── gradlew.bat ├── README.md └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | QuantumFlux -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /quantum-flux/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':quantum-flux' 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/.DS_Store -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/.DS_Store -------------------------------------------------------------------------------- /quantum-flux/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/quantum-flux/.DS_Store -------------------------------------------------------------------------------- /quantum-flux/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/quantum-flux/src/.DS_Store -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /quantum-flux/src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/quantum-flux/src/main/.DS_Store -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/hpsc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/src/main/res/drawable/hpsc.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /quantum-flux/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | quantum flux library 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himanshu-soni/QuantumFlux/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Authority.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | public @interface Authority { 4 | String value(); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/sync/SyncConstants.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.sync; 2 | 3 | public interface SyncConstants { 4 | String AUTHORITY = "me.himanshusoni.quantumflux.sample"; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Quantum Flux Sample 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Sep 06 14:06:07 IST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /quantum-flux/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/QuantumFluxDatabaseUpgradeListener.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux; 2 | 3 | /** 4 | * Created by himanshusoni on 06/09/16. 5 | */ 6 | public interface QuantumFluxDatabaseUpgradeListener { 7 | void onDatabaseUpgraded(int oldVersion, int newVersion); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/authenticator.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/syncadapter.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/References.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface References { 11 | 12 | Class value(); 13 | } 14 | -------------------------------------------------------------------------------- /quantum-flux/src/androidTest/java/me/himanshusoni/quantumflux/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/me/himanshusoni/quantumflux/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/ChangeListeners.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface ChangeListeners { 11 | Class[] changeListeners(); 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Log Files 24 | *.log 25 | 26 | # Android studio specific files 27 | /.idea/workspace.xml 28 | /.idea/libraries 29 | 30 | # for MAC OS 31 | .DS_Store 32 | /captures -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Column/Unique.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation.Column; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Indicates a field that should be marked as unique 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface Unique { 14 | } 15 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/data/Author.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.data; 2 | 3 | import me.himanshusoni.quantumflux.model.QuantumFluxRecord; 4 | import me.himanshusoni.quantumflux.model.annotation.ChangeListeners; 5 | import me.himanshusoni.quantumflux.sample.data.view.BookAuthor; 6 | 7 | /** 8 | * Created by Himanshu on 8/5/2015. 9 | */ 10 | @ChangeListeners(changeListeners = BookAuthor.class) 11 | public class Author extends QuantumFluxRecord { 12 | public String name; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/data/Publisher.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.data; 2 | 3 | import me.himanshusoni.quantumflux.model.annotation.Column.Column; 4 | import me.himanshusoni.quantumflux.model.annotation.Column.PrimaryKey; 5 | import me.himanshusoni.quantumflux.model.annotation.Table; 6 | 7 | /** 8 | * Created by Himanshu on 8/5/2015. 9 | */ 10 | 11 | @Table(tableName = "publishers") 12 | public class Publisher { 13 | @PrimaryKey 14 | public long id; 15 | 16 | @Column(required = true) 17 | public String name; 18 | } 19 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/QuantumFluxException.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | public class QuantumFluxException extends RuntimeException { 4 | 5 | public QuantumFluxException() { 6 | } 7 | 8 | public QuantumFluxException(String detailMessage) { 9 | super(detailMessage); 10 | } 11 | 12 | public QuantumFluxException(String detailMessage, Throwable throwable) { 13 | super(detailMessage, throwable); 14 | } 15 | 16 | public QuantumFluxException(Throwable throwable) { 17 | super(throwable); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Indices.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Used to indicate all of the indices to be created for a table. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface Indices { 14 | 15 | /** 16 | * A collection of indices to be created for the table 17 | */ 18 | Index[] indices(); 19 | } 20 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Column/PrimaryKey.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation.Column; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Indicates that a field is a primary key field 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface PrimaryKey { 14 | 15 | /**If this field should be auto incremented. Default is true. Requires that the field type is long*/ 16 | boolean autoIncrement() default true; 17 | } 18 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Index.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Creates the specified index for the table 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.ANNOTATION_TYPE) 13 | public @interface Index { 14 | 15 | /** 16 | * The name of this index 17 | */ 18 | String indexName(); 19 | 20 | /** 21 | * The columns that will be included in the index 22 | */ 23 | String[] indexColumns(); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/data/Book.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.data; 2 | 3 | import me.himanshusoni.quantumflux.model.annotation.ChangeListeners; 4 | import me.himanshusoni.quantumflux.model.annotation.Column.PrimaryKey; 5 | import me.himanshusoni.quantumflux.model.annotation.Table; 6 | import me.himanshusoni.quantumflux.sample.data.view.BookAuthor; 7 | 8 | /** 9 | * Created by Himanshu on 8/5/2015. 10 | */ 11 | 12 | @Table 13 | @ChangeListeners(changeListeners = BookAuthor.class) 14 | public class Book { 15 | 16 | @PrimaryKey 17 | public long id; 18 | public String name; 19 | public String isbn; 20 | 21 | public long authorId; 22 | 23 | public byte[] bookCover; 24 | } 25 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Himanshu.Lenovo-PC\Desktop\SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /quantum-flux/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Himanshu.Lenovo-PC\Desktop\SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/generate/TableView.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.generate; 2 | 3 | import me.himanshusoni.quantumflux.model.annotation.Table; 4 | 5 | /** 6 | * Interface to indicate that a Java Object should be treated as a view instead of a table 7 | * that will be auto generated. For that purpose, and due to the complexity that views can take, 8 | * the creation of the Select Statement for the view is left to implementing class. 9 | * 10 | * Note: The class should still have all of the normal {@link Table} annotations. 11 | */ 12 | public interface TableView { 13 | 14 | /** 15 | * The SELECT statement that will be used to create the view. 16 | */ 17 | String getTableViewSql(); 18 | } 19 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/data/view/BookAuthor.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.data.view; 2 | 3 | import me.himanshusoni.quantumflux.model.annotation.Column.PrimaryKey; 4 | import me.himanshusoni.quantumflux.model.annotation.Table; 5 | import me.himanshusoni.quantumflux.model.generate.TableView; 6 | 7 | /** 8 | * Created by Himanshu on 8/6/2015. 9 | */ 10 | 11 | @Table 12 | public class BookAuthor implements TableView { 13 | 14 | @Override 15 | public String getTableViewSql() { 16 | return "SELECT b._ID, b.NAME, a.NAME FROM book b INNER JOIN author a ON b.author_id = a._id"; 17 | } 18 | 19 | @PrimaryKey 20 | private int _id; 21 | 22 | private String bookName; 23 | private String authorName; 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '24.0.1' 6 | 7 | defaultConfig { 8 | applicationId "me.himanshusoni.quantumflux.sample" 9 | minSdkVersion 12 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | // compile 'info.quantumflux:library:0.9.2' 25 | compile project(':quantum-flux') 26 | compile 'com.android.support:appcompat-v7:24.2.0' 27 | } -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Table.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Marks a Java object as table model 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface Table { 14 | 15 | /** 16 | * the table name to be used for this object. The default will converted the object name and use that instead. 17 | */ 18 | String tableName() default ""; 19 | 20 | /** 21 | * Any additional constraints that should be added for the table. Todo: Not implemented yet 22 | */ 23 | TableConstraint[] constraints() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/sync/AuthenticatorService.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.sync; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | /** 8 | * A bound Service that instantiates the authenticator 9 | * when started. 10 | */ 11 | public class AuthenticatorService extends Service { 12 | // Instance field that stores the authenticator object 13 | private Authenticator mAuthenticator; 14 | 15 | @Override 16 | public void onCreate() { 17 | // Create a new authenticator object 18 | mAuthenticator = new Authenticator(this); 19 | } 20 | 21 | /* 22 | * When the system binds to this Service to make the RPC call 23 | * return the authenticator's IBinder. 24 | */ 25 | @Override 26 | public IBinder onBind(Intent intent) { 27 | return mAuthenticator.getIBinder(); 28 | } 29 | } -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/LongType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class LongType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return Long.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "INTEGER"; 17 | } 18 | 19 | @Override 20 | public Object toSqlType(Object source) { 21 | return source; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getLong(columnIndex); 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, (Long) value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /QuantumFlux.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/TableConstraint.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * An additional constraint that should be applied to the table 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.TYPE) 13 | public @interface TableConstraint { 14 | public enum Type { 15 | PRIMARY_KEY, 16 | UNIQUE 17 | } 18 | 19 | /** 20 | * The name of this constraint 21 | */ 22 | String name(); 23 | 24 | /** 25 | * The type of constraint. Primary key should not be used as currently we only support a single primary key field 26 | */ 27 | Type constraintType(); 28 | 29 | /** 30 | * The columns on which the constraint applies 31 | */ 32 | String[] constraintColumns(); 33 | } 34 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/FloatType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class FloatType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return Float.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "NUMERIC"; 17 | } 18 | 19 | @Override 20 | public Object toSqlType(Object source) { 21 | return source; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getFloat(columnIndex); 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, (Float) value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/ShortType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class ShortType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return Short.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "INTEGER"; 17 | } 18 | 19 | @Override 20 | public Object toSqlType(Object source) { 21 | return source; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getShort(columnIndex); 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, (Short) value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/DoubleType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class DoubleType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return Double.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "REAL"; 17 | } 18 | 19 | @Override 20 | public Object toSqlType(Object source) { 21 | return source; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getDouble(columnIndex); 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, (Double) value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/IntegerType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class IntegerType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return Integer.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "INTEGER"; 17 | } 18 | 19 | @Override 20 | public Object toSqlType(Object source) { 21 | return source; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getInt(columnIndex); 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, (Integer) value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/StringType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class StringType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return String.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "TEXT"; 17 | } 18 | 19 | @Override 20 | public Object toSqlType(Object source) { 21 | return (String) source; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getString(columnIndex); 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, (String) value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/query/DataFilterClause.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.query; 2 | 3 | import java.io.Serializable; 4 | 5 | import me.himanshusoni.quantumflux.model.map.SqlColumnMappingFactory; 6 | 7 | /** 8 | * Defines a basic clause interface, that can be used to join queries together 9 | */ 10 | public interface DataFilterClause extends Serializable { 11 | 12 | boolean hasFilterValue(); 13 | 14 | /** 15 | * The filter conjunction, this is equal to SQL AND and OR 16 | */ 17 | public enum DataFilterConjunction { 18 | AND, OR 19 | } 20 | 21 | /** 22 | * The where clause for this query 23 | */ 24 | QueryBuilder buildWhereClause(SqlColumnMappingFactory columnMappingFactory); 25 | 26 | /** 27 | * The where clause for this query, without parameters 28 | */ 29 | String getWhereClause(); 30 | 31 | T addClause(DataFilterClause clause, DataFilterConjunction conjunction); 32 | } 33 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/generate/TableViewGenerator.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.generate; 2 | 3 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 4 | 5 | /** 6 | * Generates the statements required to drop and create views 7 | */ 8 | public class TableViewGenerator { 9 | 10 | public static String createDropViewStatement(TableDetails tableDetails) { 11 | return "DROP VIEW IF EXISTS " + tableDetails.getTableName(); 12 | } 13 | 14 | public static String createViewStatement(TableDetails tableDetails, Class view) { 15 | TableView tableView; 16 | try { 17 | tableView = view.getConstructor().newInstance(); 18 | } catch (Exception e) { 19 | throw new QuantumFluxException("Failed to instantiate view " + view.getSimpleName(), e); 20 | } 21 | 22 | return "CREATE VIEW IF NOT EXISTS " + tableDetails.getTableName() + " AS " + tableView.getTableViewSql(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/query/DataGroupClause.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.query; 2 | 3 | import java.io.Serializable; 4 | 5 | import me.himanshusoni.quantumflux.model.map.SqlColumnMappingFactory; 6 | 7 | /** 8 | * Defines a basic group clause interface, that can be used to join queries together 9 | */ 10 | public interface DataGroupClause extends Serializable { 11 | 12 | boolean hasFilterValue(); 13 | 14 | /** 15 | * The filter conjunction, this is equal to SQL AND and OR 16 | */ 17 | public enum DataFilterConjunction { 18 | AND, OR 19 | } 20 | 21 | /** 22 | * The where clause for this query 23 | */ 24 | QueryBuilder buildWhereClause(SqlColumnMappingFactory columnMappingFactory); 25 | 26 | /** 27 | * The where clause for this query, without parameters 28 | */ 29 | String getWhereClause(); 30 | 31 | T addClause(DataGroupClause clause, DataFilterConjunction conjunction); 32 | } 33 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/DateType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import java.util.Date; 7 | 8 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 9 | 10 | public class DateType implements SqlColumnMapping { 11 | @Override 12 | public Class getJavaType() { 13 | return Date.class; 14 | } 15 | 16 | @Override 17 | public String getSqlColumnTypeName() { 18 | return "INTEGER"; 19 | } 20 | 21 | @Override 22 | public Long toSqlType(Object source) { 23 | return ((Date) source).getTime(); 24 | } 25 | 26 | @Override 27 | public Object getColumnValue(Cursor cursor, int columnIndex) { 28 | return new Date(cursor.getLong(columnIndex)); 29 | } 30 | 31 | @Override 32 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 33 | contentValues.put(key, toSqlType(value)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/UUIDType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import java.util.UUID; 7 | 8 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 9 | 10 | public class UUIDType implements SqlColumnMapping { 11 | @Override 12 | public Class getJavaType() { 13 | return UUID.class; 14 | } 15 | 16 | @Override 17 | public String getSqlColumnTypeName() { 18 | return "TEXT"; 19 | } 20 | 21 | @Override 22 | public String toSqlType(Object source) { 23 | return source.toString(); 24 | } 25 | 26 | @Override 27 | public Object getColumnValue(Cursor cursor, int columnIndex) { 28 | return UUID.fromString(cursor.getString(columnIndex)); 29 | } 30 | 31 | @Override 32 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 33 | contentValues.put(key, toSqlType(value)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/BooleanType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 7 | 8 | public class BooleanType implements SqlColumnMapping { 9 | @Override 10 | public Class getJavaType() { 11 | return Boolean.class; 12 | } 13 | 14 | @Override 15 | public String getSqlColumnTypeName() { 16 | return "INTEGER"; 17 | } 18 | 19 | @Override 20 | public Integer toSqlType(Object source) { 21 | return ((Boolean) source) ? 1 : 0; 22 | } 23 | 24 | @Override 25 | public Object getColumnValue(Cursor cursor, int columnIndex) { 26 | return cursor.getInt(columnIndex) == 0 ? Boolean.FALSE : Boolean.TRUE; 27 | } 28 | 29 | @Override 30 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 31 | contentValues.put(key, toSqlType(value)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 27 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/BigDecimalType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 9 | 10 | public class BigDecimalType implements SqlColumnMapping { 11 | @Override 12 | public Class getJavaType() { 13 | return BigDecimal.class; 14 | } 15 | 16 | @Override 17 | public String getSqlColumnTypeName() { 18 | return "TEXT"; 19 | } 20 | 21 | @Override 22 | public String toSqlType(Object source) { 23 | return (source.toString()); 24 | } 25 | 26 | @Override 27 | public Object getColumnValue(Cursor cursor, int columnIndex) { 28 | return new BigDecimal(cursor.getString(columnIndex)); 29 | } 30 | 31 | @Override 32 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 33 | contentValues.put(key, toSqlType(value)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/NamingUtils.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | /** 4 | * Contains common naming utilities to convert strings to SQL formats 5 | */ 6 | public class NamingUtils { 7 | 8 | /** 9 | * Converts the source string to a SQL compatible string. It does this 10 | * by checking for camelcase values, and applying a underscore '_' to whenever one is found. All 11 | * characters are also converted to lower case. 12 | * 13 | * Example: If the provided string is "helloWorld" the resulting string will be "hello_world" 14 | * 15 | * @param original String to converted 16 | * @return the converted string 17 | */ 18 | public static String getSQLName(String original) { 19 | StringBuilder sqlName = new StringBuilder(); 20 | 21 | for (char character : original.toCharArray()) { 22 | if (Character.isUpperCase(character) && sqlName.length() > 0) sqlName.append("_"); 23 | sqlName.append(Character.toLowerCase(character)); 24 | } 25 | return sqlName.toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/QuantumFluxCursorFactory.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.database.Cursor; 4 | import android.database.sqlite.SQLiteCursor; 5 | import android.database.sqlite.SQLiteCursorDriver; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.database.sqlite.SQLiteQuery; 8 | 9 | import me.himanshusoni.quantumflux.logger.QuantumFluxLog; 10 | 11 | public class QuantumFluxCursorFactory implements SQLiteDatabase.CursorFactory { 12 | 13 | private final boolean isDebugEnabled; 14 | 15 | public QuantumFluxCursorFactory(TableDetailsCache tableDetailCache) { 16 | this(false); 17 | } 18 | 19 | public QuantumFluxCursorFactory(boolean debugEnabled) { 20 | this.isDebugEnabled = debugEnabled; 21 | } 22 | 23 | @Override 24 | public Cursor newCursor(SQLiteDatabase sqLiteDatabase, SQLiteCursorDriver sqLiteCursorDriver, String tableName, SQLiteQuery sqLiteQuery) { 25 | if (isDebugEnabled) { 26 | QuantumFluxLog.d(sqLiteQuery.toString()); 27 | } 28 | 29 | return new SQLiteCursor(sqLiteCursorDriver, tableName, sqLiteQuery); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/types/CalendarType.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map.types; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import java.util.Calendar; 7 | 8 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 9 | 10 | public class CalendarType implements SqlColumnMapping { 11 | @Override 12 | public Class getJavaType() { 13 | return Calendar.class; 14 | } 15 | 16 | @Override 17 | public String getSqlColumnTypeName() { 18 | return "INTEGER"; 19 | } 20 | 21 | @Override 22 | public Long toSqlType(Object source) { 23 | return ((Calendar) source).getTimeInMillis(); 24 | } 25 | 26 | @Override 27 | public Object getColumnValue(Cursor cursor, int columnIndex) { 28 | long time = cursor.getLong(columnIndex); 29 | Calendar calendar = Calendar.getInstance(); 30 | calendar.setTimeInMillis(time); 31 | return calendar; 32 | } 33 | 34 | @Override 35 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 36 | contentValues.put(key, toSqlType(value)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import java.util.ArrayList; 7 | 8 | import me.himanshusoni.quantumflux.QuantumFlux; 9 | import me.himanshusoni.quantumflux.QuantumFluxDatabaseUpgradeListener; 10 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 11 | import me.himanshusoni.quantumflux.sample.data.type.BlobTypeMapping; 12 | 13 | /** 14 | * Created by Himanshu on 8/5/2015. 15 | */ 16 | public class SampleApplication extends Application { 17 | 18 | @Override 19 | protected void attachBaseContext(Context base) { 20 | super.attachBaseContext(base); 21 | 22 | ArrayList mappings = new ArrayList<>(); 23 | mappings.add(new BlobTypeMapping()); 24 | 25 | QuantumFlux.initialize(this, mappings, new QuantumFluxDatabaseUpgradeListener() { 26 | @Override 27 | public void onDatabaseUpgraded(int oldVersion, int newVersion) { 28 | 29 | } 30 | }); 31 | } 32 | 33 | @Override 34 | public void onCreate() { 35 | super.onCreate(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/annotation/Column/Column.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.annotation.Column; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Identifies a field as database column to be generated. 10 | */ 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface Column { 15 | 16 | /** 17 | * The name of the column, if none is provided, the field name will be converted and used instead 18 | */ 19 | String columnName() default ""; 20 | 21 | /** 22 | * If the field should be set required/nullable, the default is true 23 | */ 24 | boolean required() default false; 25 | 26 | /** 27 | * If this setting is set to true, changes will be notified on the content resolver, if it is false it will not. 28 | * If more than one column is passed through on updates, changes will be notified if at least one of the columns being 29 | * updated is set to true. 30 | * 31 | * @return true if changes are to be notified, false otherwise. Default is true. 32 | */ 33 | boolean notifyChanges() default true; 34 | } 35 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/QuantumFluxLoaderCallback.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader; 2 | 3 | import android.app.LoaderManager; 4 | import android.content.Context; 5 | import android.content.Loader; 6 | import android.database.Cursor; 7 | import android.os.Bundle; 8 | import android.widget.CursorAdapter; 9 | 10 | import me.himanshusoni.quantumflux.model.query.Select; 11 | 12 | public class QuantumFluxLoaderCallback implements LoaderManager.LoaderCallbacks { 13 | 14 | private final Context mContext; 15 | private final CursorAdapter mAdapter; 16 | private final Select mSelect; 17 | 18 | public QuantumFluxLoaderCallback(Context context, CursorAdapter listAdapter, Select select) { 19 | this.mContext = context; 20 | this.mAdapter = listAdapter; 21 | this.mSelect = select; 22 | } 23 | 24 | @Override 25 | public Loader onCreateLoader(int id, Bundle args) { 26 | return new QuantumFluxLoader(mContext, mSelect); 27 | } 28 | 29 | @Override 30 | public void onLoadFinished(Loader loader, Cursor data) { 31 | mAdapter.changeCursor(data); 32 | } 33 | 34 | @Override 35 | public void onLoaderReset(Loader loader) { 36 | mAdapter.changeCursor(null); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/support/QuantumFluxLoaderCallback.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader.support; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.os.Bundle; 6 | import android.support.v4.app.LoaderManager; 7 | import android.support.v4.content.Loader; 8 | import android.support.v4.widget.CursorAdapter; 9 | 10 | import me.himanshusoni.quantumflux.model.query.Select; 11 | 12 | 13 | public class QuantumFluxLoaderCallback implements LoaderManager.LoaderCallbacks { 14 | 15 | private final Context mContext; 16 | private final CursorAdapter mAdapter; 17 | private final Select mSelect; 18 | 19 | public QuantumFluxLoaderCallback(Context context, CursorAdapter listAdapter, Select select) { 20 | this.mContext = context; 21 | this.mAdapter = listAdapter; 22 | this.mSelect = select; 23 | } 24 | 25 | @Override 26 | public Loader onCreateLoader(int id, Bundle args) { 27 | return new QuantumFluxLoader(mContext, mSelect); 28 | } 29 | 30 | @Override 31 | public void onLoadFinished(Loader loader, Cursor data) { 32 | mAdapter.changeCursor(data); 33 | } 34 | 35 | @Override 36 | public void onLoaderReset(Loader loader) { 37 | mAdapter.changeCursor(null); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/ContentResolverValues.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.net.Uri; 4 | 5 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 6 | 7 | public class ContentResolverValues { 8 | 9 | private final TableDetails mTableDetails; 10 | private final Uri mUri; 11 | private final String[] mProjection; 12 | private final String mWhere; 13 | private final String[] mWhereArgs; 14 | private final String mSortOrder; 15 | 16 | public ContentResolverValues(TableDetails tableDetails, Uri itemUri, String[] projection, String where, String[] whereArgs, String sortOrder) { 17 | this.mTableDetails = tableDetails; 18 | this.mUri = itemUri; 19 | this.mProjection = projection; 20 | this.mWhere = where; 21 | this.mWhereArgs = whereArgs; 22 | this.mSortOrder = sortOrder; 23 | } 24 | 25 | public TableDetails getTableDetails() { 26 | return mTableDetails; 27 | } 28 | 29 | public Uri getItemUri() { 30 | return mUri; 31 | } 32 | 33 | public String[] getProjection() { 34 | return mProjection; 35 | } 36 | 37 | public String getWhere() { 38 | return mWhere; 39 | } 40 | 41 | public String[] getWhereArgs() { 42 | return mWhereArgs; 43 | } 44 | 45 | public String getSortOrder() { 46 | return mSortOrder; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/query/SQLSegment.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.query; 2 | 3 | import android.text.TextUtils; 4 | 5 | import me.himanshusoni.quantumflux.model.map.SqlColumnMappingFactory; 6 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 7 | 8 | public class SQLSegment implements DataFilterClause { 9 | 10 | private final String mSqlSegment; 11 | private final Object[] mArgs; 12 | 13 | public SQLSegment(String sqlSegment, Object... args) { 14 | this.mSqlSegment = sqlSegment; 15 | this.mArgs = args; 16 | } 17 | 18 | @Override 19 | public QueryBuilder buildWhereClause(SqlColumnMappingFactory columnMappingFactory) { 20 | for (int i = 0; i < mArgs.length; i++) { 21 | Object argObject = mArgs[i]; 22 | mArgs[i] = columnMappingFactory.findColumnMapping(argObject.getClass()).toSqlType(argObject); 23 | } 24 | return new QueryBuilder(mSqlSegment, mArgs); 25 | } 26 | 27 | 28 | @Override 29 | public String getWhereClause() { 30 | return mSqlSegment; 31 | } 32 | 33 | @Override 34 | public SQLSegment addClause(DataFilterClause clause, DataFilterConjunction conjunction) { 35 | throw new QuantumFluxException("Clauses cannot be added to a data filter criterion"); 36 | } 37 | 38 | @Override 39 | public boolean hasFilterValue() { 40 | return !TextUtils.isEmpty(mSqlSegment); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /quantum-flux/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '24.0.1' 6 | resourcePrefix "qf_" 7 | 8 | defaultConfig { 9 | minSdkVersion 12 10 | targetSdkVersion 24 11 | versionCode 3 12 | versionName "0.9.3" 13 | } 14 | buildTypes { 15 | } 16 | } 17 | 18 | ext { 19 | bintrayRepo = 'maven' 20 | bintrayName = 'quantum-flux' 21 | 22 | publishedGroupId = 'me.himanshusoni' 23 | libraryName = 'quantum-flux' 24 | artifact = 'quantum-flux' 25 | 26 | libraryDescription = 'A Powerful Android Content Provider ORM' 27 | 28 | siteUrl = 'https://github.com/himanshu-soni/QuantumFlux' 29 | gitUrl = 'https://github.com/himanshu-soni/QuantumFlux.git' 30 | 31 | libraryVersion = '0.9.3' 32 | 33 | developerId = 'himanshu-soni' 34 | developerName = 'Himanshu Soni' 35 | developerEmail = 'himanshusoni.me@gmail.com' 36 | 37 | licenseName = 'The Apache Software License, Version 2.0' 38 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 39 | allLicenses = ["Apache-2.0"] 40 | } 41 | 42 | apply from: 'https://raw.githubusercontent.com/himanshu-soni/QuantityView/master/install.gradle' 43 | apply from: 'https://raw.githubusercontent.com/himanshu-soni/QuantityView/master/bintray.gradle' 44 | 45 | 46 | dependencies { 47 | compile fileTree(include: ['*.jar'], dir: 'libs') 48 | compile 'com.android.support:support-v4:24.2.0' 49 | } 50 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/logger/QuantumFluxLog.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.logger; 2 | 3 | import android.util.Log; 4 | 5 | public class QuantumFluxLog { 6 | 7 | private final static String TAG = "QuantumFlux"; 8 | 9 | public static int println(int level, String msg) { 10 | return Log.println(level, TAG, msg); 11 | } 12 | 13 | public static int v(String msg) { 14 | return Log.v(TAG, msg); 15 | } 16 | 17 | public static int v(String msg, Throwable th) { 18 | return Log.v(TAG, msg, th); 19 | } 20 | 21 | public static int d(String msg) { 22 | return Log.d(TAG, msg); 23 | } 24 | 25 | public static int d(String msg, Throwable th) { 26 | return Log.d(TAG, msg, th); 27 | } 28 | 29 | public static int i(String msg) { 30 | return Log.i(TAG, msg); 31 | } 32 | 33 | public static int i(String msg, Throwable th) { 34 | return Log.i(TAG, msg, th); 35 | } 36 | 37 | public static int w(String msg) { 38 | return Log.w(TAG, msg); 39 | } 40 | 41 | public static int w(String msg, Throwable th) { 42 | return Log.w(TAG, msg, th); 43 | } 44 | 45 | public static int w(Throwable th) { 46 | return Log.w(TAG, th); 47 | } 48 | 49 | public static int e(String msg) { 50 | return Log.w(TAG, msg); 51 | } 52 | 53 | public static int e(String msg, Throwable th) { 54 | return Log.e(TAG, msg, th); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/QuantumFluxBaseRecord.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model; 2 | 3 | import android.content.ContentProviderOperation; 4 | 5 | import java.util.Iterator; 6 | 7 | import me.himanshusoni.quantumflux.QuantumFlux; 8 | 9 | /** 10 | * This class is just a wrapper for {@link QuantumFlux}, sub classes can extend this 11 | * to invoke the basic crud operations on the class itself. 12 | */ 13 | public abstract class QuantumFluxBaseRecord { 14 | 15 | public QuantumFluxBaseRecord() { 16 | } 17 | 18 | public Iterator findAll() { 19 | return (Iterator) QuantumFlux.findAll(getClass()); 20 | } 21 | 22 | public T findByPrimaryKey(Object key) { 23 | return (T) QuantumFlux.findByPrimaryKey(getClass(), key); 24 | } 25 | 26 | public void insert() { 27 | QuantumFlux.insert(this); 28 | } 29 | 30 | public ContentProviderOperation prepareInsert() { 31 | return QuantumFlux.prepareInsert(this); 32 | } 33 | 34 | public T insertAndReturn() { 35 | return (T) QuantumFlux.insertAndReturn(this); 36 | } 37 | 38 | public void update() { 39 | QuantumFlux.update(this); 40 | } 41 | 42 | public ContentProviderOperation prepareUpdate() { 43 | return QuantumFlux.prepareUpdate(this); 44 | } 45 | 46 | public void delete() { 47 | QuantumFlux.delete(this); 48 | } 49 | 50 | public ContentProviderOperation prepareDelete() { 51 | return QuantumFlux.prepareDelete(this); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/sync/SyncService.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.sync; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | 7 | /** 8 | * Created by Himanshu on 7/29/2015. 9 | */ 10 | public class SyncService extends Service { 11 | // Storage for an instance of the sync adapter 12 | private static SyncAdapter sSyncAdapter = null; 13 | // Object to use as a thread-safe lock 14 | private static final Object sSyncAdapterLock = new Object(); 15 | 16 | /* 17 | * Instantiate the sync adapter object. 18 | */ 19 | @Override 20 | public void onCreate() { 21 | /* 22 | * Create the sync adapter as a singleton. 23 | * Set the sync adapter as syncable 24 | * Disallow parallel syncs 25 | */ 26 | synchronized (sSyncAdapterLock) { 27 | if (sSyncAdapter == null) { 28 | sSyncAdapter = new SyncAdapter(getApplicationContext(), true); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Return an object that allows the system to invoke 35 | * the sync adapter. 36 | */ 37 | @Override 38 | public IBinder onBind(Intent intent) { 39 | /* 40 | * Get the object that allows external processes 41 | * to call onPerformSync(). The object is created 42 | * in the base class code when the SyncAdapter 43 | * constructors call super() 44 | */ 45 | return sSyncAdapter.getSyncAdapterBinder(); 46 | } 47 | } -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/SqlColumnMapping.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | /** 7 | * This allows us to handle the mapping of objects to and from sql 8 | * in an easy way without having to worry about the implicit type 9 | */ 10 | public interface SqlColumnMapping { 11 | 12 | /** The java type this mapping will represent */ 13 | Class getJavaType(); 14 | 15 | /** The SQL equivalent column name that will be used to store this type */ 16 | String getSqlColumnTypeName(); 17 | 18 | /** Converts the source object to the correct representation to be stored in the sql database. 19 | * For example, a Date might be stored as a millisecond long, this will convert the date object to milliseconds. 20 | */ 21 | Object toSqlType(Object source); 22 | 23 | /** Gets the appropriate column value from the column in the cursor, it will then 24 | * convert that to the correct java type. 25 | * For example, a Date might be stored as a millisecond long, this will take the long, and a create a new Data object. 26 | */ 27 | Object getColumnValue(Cursor cursor, int columnIndex); 28 | 29 | /** 30 | * This will convert and set the correct object type for supplied value. 31 | * An explanation for the conversion can be found on toSqlType(Object source); 32 | * @param contentValues The content values that the object will be placed in 33 | * @param key The key that the object should placed into 34 | * @param value The source object to be converted 35 | */ 36 | void setColumnValue(ContentValues contentValues, String key, Object value); 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 25 | 26 | 35 | 36 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/query/QueryBuilder.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.query; 2 | 3 | import java.io.Serializable; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.Collections; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | /** 11 | * This class will keep track of the query and its supplied arguments , so that when multiple 12 | * queries are appended, the query and arguments will always match up. 13 | */ 14 | public class QueryBuilder implements Serializable { 15 | 16 | private final StringBuilder mQueryBuffer; 17 | private final List mArgsStore; 18 | 19 | public QueryBuilder() { 20 | mQueryBuffer = new StringBuilder(); 21 | mArgsStore = new LinkedList<>(); 22 | } 23 | 24 | public QueryBuilder(String init, Object... args) { 25 | mQueryBuffer = new StringBuilder(init); 26 | mArgsStore = new LinkedList<>(Arrays.asList(args)); 27 | } 28 | 29 | public void append(String query, Object... args) { 30 | mQueryBuffer.append(query); 31 | for (Object arg : args) { 32 | mArgsStore.add(String.valueOf(arg)); 33 | } 34 | } 35 | 36 | public void append(QueryBuilder queryBuilder) { 37 | mQueryBuffer.append(queryBuilder.getQueryString()); 38 | mArgsStore.addAll(queryBuilder.getQueryArgs()); 39 | } 40 | 41 | public String getQueryString() { 42 | return String.valueOf(mQueryBuffer); 43 | } 44 | 45 | private Collection getQueryArgs() { 46 | List queryArgs = new LinkedList<>(); 47 | queryArgs.addAll(mArgsStore); 48 | 49 | return Collections.unmodifiableCollection(queryArgs); 50 | } 51 | 52 | public String[] getQueryArgsAsArray() { 53 | String[] args = new String[mArgsStore.size()]; 54 | 55 | for (int i = 0; i < mArgsStore.size(); i++) { 56 | args[i] = String.valueOf(mArgsStore.get(i)); 57 | } 58 | return args; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return getQueryString(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/ReferenceMap.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | 4 | import java.lang.ref.SoftReference; 5 | import java.lang.reflect.Field; 6 | import java.util.IdentityHashMap; 7 | 8 | import me.himanshusoni.quantumflux.QuantumFlux; 9 | import me.himanshusoni.quantumflux.model.annotation.References; 10 | import me.himanshusoni.quantumflux.model.generate.ReflectionHelper; 11 | 12 | public class ReferenceMap extends IdentityHashMap, SoftReference> { 13 | 14 | private final Object mReferenceObject; 15 | 16 | public ReferenceMap(Object referenceObject) { 17 | this.mReferenceObject = referenceObject; 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | public T findReferent(Class referenceToFind) { 22 | if (containsKey(referenceToFind)) { 23 | SoftReference softReference = super.get(referenceToFind); 24 | Object referent = softReference.get(); 25 | if (referent != null) { 26 | return (T) referent; 27 | } 28 | } 29 | 30 | for (Field field : ReflectionHelper.getAllObjectFields(mReferenceObject.getClass())) { 31 | try { 32 | if (field.isAnnotationPresent(References.class) && field.getAnnotation(References.class).value() == referenceToFind) { 33 | if (!field.isAccessible()) { 34 | field.setAccessible(true); 35 | } 36 | 37 | T reference = QuantumFlux.findByPrimaryKey(referenceToFind, field.get(mReferenceObject)); 38 | 39 | if (reference != null) { 40 | put(referenceToFind, new SoftReference(reference)); 41 | } 42 | 43 | return reference; 44 | } 45 | } catch (IllegalAccessException e) { 46 | throw new QuantumFluxException("Could not access required field " + field.getName(), e); 47 | } 48 | } 49 | throw new QuantumFluxException("No Reference found to " + referenceToFind.getSimpleName() + " from " + mReferenceObject.getClass().getSimpleName()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/sync/SyncAdapter.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.sync; 2 | 3 | import android.accounts.Account; 4 | import android.content.AbstractThreadedSyncAdapter; 5 | import android.content.ContentProviderClient; 6 | import android.content.ContentResolver; 7 | import android.content.Context; 8 | import android.content.SyncResult; 9 | import android.os.Bundle; 10 | 11 | /** 12 | * Handle the transfer of data between a server and an 13 | * app, using the Android sync adapter framework. 14 | */ 15 | 16 | public class SyncAdapter extends AbstractThreadedSyncAdapter { 17 | 18 | // Global variables 19 | // Define a variable to contain a content resolver instance 20 | ContentResolver mContentResolver; 21 | 22 | /** 23 | * Set up the sync adapter 24 | */ 25 | public SyncAdapter(Context context, boolean autoInitialize) { 26 | super(context, autoInitialize); 27 | /* 28 | * If your app uses a content resolver, get an instance of it 29 | * from the incoming Context 30 | */ 31 | mContentResolver = context.getContentResolver(); 32 | } 33 | 34 | /** 35 | * Set up the sync adapter. This form of the 36 | * constructor maintains compatibility with Android 3.0 37 | * and later platform versions 38 | */ 39 | public SyncAdapter( 40 | Context context, 41 | boolean autoInitialize, 42 | boolean allowParallelSyncs) { 43 | super(context, autoInitialize, allowParallelSyncs); 44 | /* 45 | * If your app uses a content resolver, get an instance of it 46 | * from the incoming Context 47 | */ 48 | mContentResolver = context.getContentResolver(); 49 | } 50 | 51 | /* 52 | * Specify the code you want to run in the sync adapter. The entire 53 | * sync adapter runs in a background thread, so you don't have to set 54 | * up your own background processing. 55 | */ 56 | @Override 57 | public void onPerformSync( 58 | Account account, 59 | Bundle extras, 60 | String authority, 61 | ContentProviderClient provider, 62 | SyncResult syncResult) { 63 | /* 64 | * Put the data transfer code here. 65 | */ 66 | 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/CursorIterator.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.database.Cursor; 4 | import android.database.SQLException; 5 | 6 | import java.io.Closeable; 7 | import java.io.IOException; 8 | import java.util.Iterator; 9 | 10 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 11 | 12 | /** 13 | * The iterator will just iterator over a mCursor, it does that by checking in the has next method 14 | * if the mCursor is open and not after the last item. If the mCursor is can fetch a next item, that item is returned in next, 15 | * if the returned item is the last one, the mCursor is automatically closed. 16 | */ 17 | public class CursorIterator implements Iterator, Closeable { 18 | private final TableDetails mTableDetails; 19 | private final Cursor mCursor; 20 | 21 | public CursorIterator(TableDetails tableDetails, Cursor cursor) { 22 | this.mTableDetails = tableDetails; 23 | this.mCursor = cursor; 24 | } 25 | 26 | @Override 27 | public boolean hasNext() { 28 | if (mCursor != null && mCursor.isAfterLast()) 29 | mCursor.close();//Close the mCursor if we reached the last position 30 | 31 | return mCursor != null && !mCursor.isClosed() && !mCursor.isAfterLast(); 32 | } 33 | 34 | @Override 35 | public T next() { 36 | T entity = null; 37 | if (mCursor == null || mCursor.isAfterLast()) { 38 | throw new QuantumFluxException(); 39 | } 40 | 41 | try { 42 | if (mCursor.isBeforeFirst()) { 43 | mCursor.moveToFirst(); 44 | } 45 | 46 | try { 47 | entity = ModelInflater.inflate(mCursor, mTableDetails); 48 | } finally { 49 | mCursor.moveToNext(); 50 | } 51 | } catch (SQLException sqle) { 52 | mCursor.close(); 53 | throw sqle; 54 | } 55 | 56 | return entity; 57 | } 58 | 59 | @Override 60 | public void remove() { 61 | throw new UnsupportedOperationException(); 62 | } 63 | 64 | @Override 65 | public void close() throws IOException { 66 | if (!mCursor.isClosed()) { 67 | mCursor.close(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/TableDetailsCache.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import me.himanshusoni.quantumflux.model.generate.ReflectionHelper; 10 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 11 | 12 | /** 13 | * This class will maintain a mCache of all the java objects and their relevant table details, 14 | * the main function of this is to reduce the amount of times we have to use reflection to get the table details. 15 | * All of the table details are loaded on demand. The methods on this class is synchronized to prevent 16 | * multiple threads from altering the mCache at the same time. 17 | */ 18 | public class TableDetailsCache { 19 | 20 | private final Map, TableDetails> mCache; 21 | 22 | public TableDetailsCache() { 23 | mCache = new HashMap, TableDetails>(); 24 | } 25 | 26 | /** 27 | * Initializes the mCache with all of the supplied entries 28 | * 29 | * @param objects The objects for which to retrieve table details 30 | */ 31 | public synchronized void init(Context context, List> objects) { 32 | for (int i = 0; i < objects.size(); i++) { 33 | Class object = objects.get(i); 34 | findTableDetails(context, object); 35 | } 36 | } 37 | 38 | /** 39 | * Attempts to find the table details for the supplied object from the local mCache. 40 | * 41 | * @param object The object to find the table details for 42 | * @return The {@link TableDetails} for the supplied object if it is found 43 | */ 44 | public synchronized TableDetails findTableDetails(Context context, Class object) { 45 | 46 | if (!mCache.containsKey(object)) { 47 | try { 48 | mCache.put(object, ReflectionHelper.getTableDetails(context, object)); 49 | } catch (Exception ex) { 50 | throw new QuantumFluxException("Failed load table details for object " + object.getSimpleName(), ex); 51 | } 52 | 53 | //Check if it exists after we attempted to add it 54 | if (!mCache.containsKey(object)) 55 | throw new QuantumFluxException("No table details could be found for supplied object: " + object.getSimpleName()); 56 | } 57 | 58 | return mCache.get(object); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/QuantumFluxRecord.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model; 2 | 3 | import android.content.ContentUris; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | 7 | import java.io.Serializable; 8 | 9 | import me.himanshusoni.quantumflux.QuantumFlux; 10 | import me.himanshusoni.quantumflux.model.annotation.Column.Column; 11 | import me.himanshusoni.quantumflux.model.annotation.Column.PrimaryKey; 12 | 13 | /** 14 | * Basic Content Provider Record implementation that contains a id field 15 | * that will be android list view compatible, and some helper methods. 16 | * This class implements Serializable, so domain objects can be passed as 17 | * serializable objects in android bundles. 18 | */ 19 | public abstract class QuantumFluxRecord extends QuantumFluxBaseRecord implements Serializable { 20 | 21 | @Column(columnName = "_id") 22 | @PrimaryKey(autoIncrement = true) 23 | protected Long _id; 24 | 25 | /** 26 | * Finds a record based on the id column 27 | * 28 | * @param context Current context 29 | * @param id the id of the record to find 30 | * @return The record, if found. 31 | */ 32 | public static T findById(Context context, Class object, long id) { 33 | return QuantumFlux.findByPrimaryKey(object, id); 34 | } 35 | 36 | /** 37 | * @see #findById(Context, Class, long) 38 | */ 39 | public static T findById(Class object, long id) { 40 | return QuantumFlux.findByPrimaryKey(object, id); 41 | } 42 | 43 | /** 44 | * Checks if this record has an id, if the id is present this record will be updated, 45 | * if it is null, it will be inserted instead, and the inserted id assigned to this one. 46 | * 47 | * @param context The context used to save the record. 48 | */ 49 | public void save(Context context) { 50 | if (_id == null) { 51 | _id = QuantumFlux.insertAndReturn(this)._id; 52 | } else { 53 | QuantumFlux.update(this); 54 | } 55 | } 56 | 57 | /** 58 | * @see #save(Context) 59 | */ 60 | public void save() { 61 | save(QuantumFlux.getApplicationContext()); 62 | } 63 | 64 | public Long getId() { 65 | return _id; 66 | } 67 | 68 | public Uri getItemUri() { 69 | return ContentUris.withAppendedId(QuantumFlux.getItemUri(getClass()), getId()); 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return getClass().getSimpleName() + ": " + getId(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/sync/Authenticator.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.sync; 2 | 3 | import android.accounts.AbstractAccountAuthenticator; 4 | import android.accounts.Account; 5 | import android.accounts.AccountAuthenticatorResponse; 6 | import android.accounts.NetworkErrorException; 7 | import android.content.Context; 8 | import android.os.Bundle; 9 | 10 | public class Authenticator extends AbstractAccountAuthenticator { 11 | // Simple constructor 12 | public Authenticator(Context context) { 13 | super(context); 14 | } 15 | 16 | // Editing properties is not supported 17 | @Override 18 | public Bundle editProperties(AccountAuthenticatorResponse r, String s) { 19 | throw new UnsupportedOperationException(); 20 | } 21 | 22 | // Don't add additional accounts 23 | @Override 24 | public Bundle addAccount( 25 | AccountAuthenticatorResponse r, 26 | String s, 27 | String s2, 28 | String[] strings, 29 | Bundle bundle) throws NetworkErrorException { 30 | return null; 31 | } 32 | 33 | // Ignore attempts to confirm credentials 34 | @Override 35 | public Bundle confirmCredentials( 36 | AccountAuthenticatorResponse r, 37 | Account account, 38 | Bundle bundle) throws NetworkErrorException { 39 | return null; 40 | } 41 | 42 | // Getting an authentication token is not supported 43 | @Override 44 | public Bundle getAuthToken( 45 | AccountAuthenticatorResponse r, 46 | Account account, 47 | String s, 48 | Bundle bundle) throws NetworkErrorException { 49 | throw new UnsupportedOperationException(); 50 | } 51 | 52 | // Getting a label for the auth token is not supported 53 | @Override 54 | public String getAuthTokenLabel(String s) { 55 | throw new UnsupportedOperationException(); 56 | } 57 | 58 | // Updating user credentials is not supported 59 | @Override 60 | public Bundle updateCredentials( 61 | AccountAuthenticatorResponse r, 62 | Account account, 63 | String s, Bundle bundle) throws NetworkErrorException { 64 | throw new UnsupportedOperationException(); 65 | } 66 | 67 | // Checking features for the account is not supported 68 | @Override 69 | public Bundle hasFeatures( 70 | AccountAuthenticatorResponse r, 71 | Account account, String[] strings) throws NetworkErrorException { 72 | throw new UnsupportedOperationException(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/QuantumFluxCursorAdaptor.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.CursorAdapter; 9 | 10 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursor; 11 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 12 | 13 | /** 14 | * A cursor adaptor that will automatically handle view and view holder creation. 15 | * Extend this class and implement the abstract methods. 16 | * T = Domain Model Object 17 | * K = View Holder Class 18 | */ 19 | public abstract class QuantumFluxCursorAdaptor extends CursorAdapter { 20 | 21 | private final int mLayoutId; 22 | 23 | public QuantumFluxCursorAdaptor(Context context, int layoutId) { 24 | this(context, null, layoutId); 25 | } 26 | 27 | public QuantumFluxCursorAdaptor(Context context, Cursor c, int layoutId) { 28 | this(context, c, layoutId, 0); 29 | } 30 | 31 | public QuantumFluxCursorAdaptor(Context context, Cursor c, int layoutId, int flags) { 32 | super(context, c, flags); 33 | this.mLayoutId = layoutId; 34 | } 35 | 36 | @Override 37 | public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { 38 | View view = LayoutInflater.from(context).inflate(mLayoutId, viewGroup, false); 39 | K viewHolder = createViewHolder(view); 40 | view.setTag(viewHolder); 41 | 42 | bindView(view, context, cursor); 43 | return view; 44 | } 45 | 46 | @Override 47 | public void bindView(View view, Context context, Cursor cursor) { 48 | K viewHolder = (K) view.getTag(); 49 | setViewInformation(viewHolder, ((QuantumFluxCursor) cursor).inflate()); 50 | } 51 | 52 | public abstract K createViewHolder(View view); 53 | 54 | public abstract void setViewInformation(K viewHolder, T information); 55 | 56 | @Override 57 | public void changeCursor(Cursor cursor) { 58 | if (cursor instanceof QuantumFluxCursor || cursor == null) { 59 | super.changeCursor(cursor); 60 | } else { 61 | throw new QuantumFluxException("The cursor is not of the instance " + QuantumFluxCursor.class.getSimpleName()); 62 | } 63 | } 64 | 65 | /** 66 | * Returns the inflated item at the cursor position 67 | * 68 | * @param position The position to inflate 69 | * @return The inflated item if found, null otherwise 70 | */ 71 | public T getInflatedItem(int position) { 72 | QuantumFluxCursor cursor = (QuantumFluxCursor) getCursor(); 73 | return cursor != null && cursor.moveToPosition(position) ? cursor.inflate() : null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 20 | 23 | 26 | 29 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/support/QuantumFluxCursorAdaptor.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader.support; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.support.v4.widget.CursorAdapter; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursor; 11 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 12 | 13 | /** 14 | * A mCursor adaptor that will automatically handle view and view holder creation. 15 | * Extend this class and implement the abstract methods. 16 | * T = Domain Model Object 17 | * K = View Holder Class 18 | */ 19 | public abstract class QuantumFluxCursorAdaptor extends CursorAdapter { 20 | 21 | private final int mLayoutId; 22 | 23 | public QuantumFluxCursorAdaptor(Context context, int layoutId) { 24 | this(context, null, layoutId); 25 | } 26 | 27 | public QuantumFluxCursorAdaptor(Context context, Cursor c, int layoutId) { 28 | this(context, c, layoutId, 0); 29 | } 30 | 31 | public QuantumFluxCursorAdaptor(Context context, Cursor c, int layoutId, int flags) { 32 | super(context, c, flags); 33 | this.mLayoutId = layoutId; 34 | } 35 | 36 | @Override 37 | public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { 38 | View view = LayoutInflater.from(context).inflate(mLayoutId, viewGroup, false); 39 | K viewHolder = createViewHolder(view); 40 | view.setTag(viewHolder); 41 | 42 | bindView(view, context, cursor); 43 | return view; 44 | } 45 | 46 | @Override 47 | public void bindView(View view, Context context, Cursor cursor) { 48 | K viewHolder = (K) view.getTag(); 49 | setViewInformation(viewHolder, ((QuantumFluxCursor) cursor).inflate()); 50 | } 51 | 52 | public abstract K createViewHolder(View view); 53 | 54 | public abstract void setViewInformation(K viewHolder, T information); 55 | 56 | @Override 57 | public void changeCursor(Cursor cursor) { 58 | if (cursor instanceof QuantumFluxCursor || cursor == null) { 59 | super.changeCursor(cursor); 60 | } else 61 | throw new QuantumFluxException("The mCursor is not of the instance " + QuantumFluxCursor.class.getSimpleName()); 62 | } 63 | 64 | /** 65 | * Returns the inflated item at the mCursor position 66 | * 67 | * @param position The position to inflate 68 | * @return The inflated item if found, null otherwise 69 | */ 70 | public T getInflatedItem(int position) { 71 | QuantumFluxCursor cursor = (QuantumFluxCursor) getCursor(); 72 | if (cursor.moveToPosition(position)) { 73 | return cursor.inflate(); 74 | } 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/data/type/BlobTypeMapping.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.data.type; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | import android.util.Log; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.Closeable; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.ObjectInputStream; 13 | import java.io.ObjectOutputStream; 14 | 15 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 16 | 17 | /** 18 | * Created by Himanshu on 8/5/2015. 19 | */ 20 | public class BlobTypeMapping implements SqlColumnMapping { 21 | 22 | @Override 23 | public Class getJavaType() { 24 | return byte[].class; 25 | } 26 | 27 | @Override 28 | public String getSqlColumnTypeName() { 29 | return "BLOB"; 30 | } 31 | 32 | @Override 33 | public Object toSqlType(Object source) { 34 | ByteArrayOutputStream outputStream = null; 35 | ObjectOutputStream objectOutputStream = null; 36 | try { 37 | outputStream = new ByteArrayOutputStream(); 38 | objectOutputStream = new ObjectOutputStream(outputStream); 39 | objectOutputStream.writeObject(source); 40 | objectOutputStream.flush(); 41 | return outputStream.toByteArray(); 42 | } catch (IOException io) { 43 | Log.e(getClass().getSimpleName(), "Failed to serialize object for storage", io); 44 | return null; 45 | } finally { 46 | closeStreams(outputStream, objectOutputStream); 47 | } 48 | } 49 | 50 | @Override 51 | public Object getColumnValue(Cursor cursor, int columnIndex) { 52 | byte[] columnValue = cursor.getBlob(columnIndex); 53 | InputStream inputStream = null; 54 | ObjectInputStream objectInputStream = null; 55 | 56 | try { 57 | inputStream = new ByteArrayInputStream(columnValue); 58 | objectInputStream = new ObjectInputStream(inputStream); 59 | 60 | return objectInputStream.readObject(); 61 | } catch (Exception ex) { 62 | Log.e(getClass().getSimpleName(), "Failed to deserialize object", ex); 63 | } finally { 64 | closeStreams(inputStream, objectInputStream); 65 | } 66 | return null; 67 | } 68 | 69 | @Override 70 | public void setColumnValue(ContentValues contentValues, String key, Object value) { 71 | contentValues.put(key, (byte[]) toSqlType(value)); 72 | } 73 | 74 | private void closeStreams(Closeable... streams) { 75 | try { 76 | for (Closeable stream : streams) { 77 | if (stream != null) stream.close(); 78 | } 79 | } catch (IOException e) { 80 | Log.e(getClass().getSimpleName(), "Failed to close streams"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/sync/SyncUtils.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample.sync; 2 | 3 | import android.accounts.Account; 4 | import android.accounts.AccountManager; 5 | import android.content.ContentResolver; 6 | import android.content.Context; 7 | import android.os.Bundle; 8 | 9 | /** 10 | * Created by Himanshu on 8/6/2015. 11 | */ 12 | public class SyncUtils { 13 | 14 | public static final String ACCOUNT_TYPE = "quantumflux"; 15 | public static final long SYNC_INTERVAL = 60L * 60L * 1; 16 | 17 | /** 18 | * Create a new dummy account for the sync adapter 19 | * 20 | * @param context The application context 21 | */ 22 | public static Account createSyncAccount(Context context, String name) { 23 | // Create the account type and default account 24 | Account newAccount = new Account(name, ACCOUNT_TYPE); 25 | // Get an instance of the Android account manager 26 | AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 27 | /* 28 | * Add the account and account type, no password or user data 29 | * If successful, return the Account object, otherwise report an error. 30 | */ 31 | if (accountManager.addAccountExplicitly(newAccount, null, null)) { 32 | /* 33 | * If you don't set android:syncable="true" in 34 | * in your element in the manifest, 35 | * then call context.setIsSyncable(account, CONTENT_AUTHORITY, 1) 36 | * here. 37 | */ 38 | // Turn on automatic syncing for the default account and authority 39 | // mResolver.setSyncAutomatically(newAccount, SyncConstants.AUTHORITY, true); 40 | } else { 41 | /* 42 | * The account exists or some other error occurred. Log this, report it, 43 | * or handle it internally. 44 | */ 45 | } 46 | 47 | return newAccount; 48 | } 49 | 50 | public static void refreshManually(Account account) { 51 | // Pass the settings flags by inserting them in a bundle 52 | Bundle settingsBundle = new Bundle(); 53 | settingsBundle.putBoolean( 54 | ContentResolver.SYNC_EXTRAS_MANUAL, true); 55 | settingsBundle.putBoolean( 56 | ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 57 | /* 58 | * Request the sync for the default account, authority, and 59 | * manual sync settings 60 | */ 61 | ContentResolver.requestSync(account, SyncConstants.AUTHORITY, settingsBundle); 62 | } 63 | 64 | /** 65 | * Turn on periodic syncing 66 | */ 67 | public static void startPeriodicSync(Account account) { 68 | ContentResolver.addPeriodicSync( 69 | account, 70 | SyncConstants.AUTHORITY, 71 | Bundle.EMPTY, 72 | SYNC_INTERVAL); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/support/QuantumFluxLoader.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader.support; 2 | 3 | import android.content.Context; 4 | import android.support.v4.content.CursorLoader; 5 | 6 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 7 | import me.himanshusoni.quantumflux.model.query.Select; 8 | import me.himanshusoni.quantumflux.model.util.ContentResolverValues; 9 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursor; 10 | 11 | public class QuantumFluxLoader extends CursorLoader { 12 | 13 | private TableDetails mTableDetails; 14 | private int mCacheSize = 0; 15 | 16 | /** 17 | * Creates a new mCursor loader using the select statement provided. The default implementation 18 | * will enable the cache of the mCursor to improve view performance. To manually specify the 19 | * mCursor cache size, use the overloaded constructor. 20 | * 21 | * @param context The context that will be used to create the mCursor. 22 | * @param select The select statement that will be used to retrieve the data. 23 | */ 24 | public QuantumFluxLoader(Context context, Select select) { 25 | super(context); 26 | 27 | ContentResolverValues resolverValues = select.asContentResolverValue(); 28 | setUri(resolverValues.getItemUri()); 29 | setProjection(resolverValues.getProjection()); 30 | setSelection(resolverValues.getWhere()); 31 | setSelectionArgs(resolverValues.getWhereArgs()); 32 | setSortOrder(resolverValues.getSortOrder()); 33 | 34 | mTableDetails = resolverValues.getTableDetails(); 35 | } 36 | 37 | /** 38 | * Creates a new mCursor loader using the select statement provided. You 39 | * can specify the cache size to use, or use -1 to disable mCursor caching. 40 | * 41 | * @param context The context that will be used to create the mCursor. 42 | * @param select The select statement that will be used to retrieve the data. 43 | * @param cacheSize The cache size for the mCursor, or -1 to disable caching 44 | */ 45 | public QuantumFluxLoader(Context context, Select select, int cacheSize) { 46 | this(context, select); 47 | 48 | enableCursorCache(cacheSize); 49 | } 50 | 51 | public void enableCursorCache(int size) { 52 | mCacheSize = size; 53 | } 54 | 55 | @Override 56 | public QuantumFluxCursor loadInBackground() { 57 | QuantumFluxCursor cursor = new QuantumFluxCursor<>(mTableDetails, super.loadInBackground()); 58 | 59 | if (mCacheSize == 0) { 60 | cursor.enableCache(); 61 | } else if (mCacheSize > 0) { 62 | cursor.enableCache(mCacheSize); 63 | } 64 | 65 | //Prefetch at least some items in preparation for the list 66 | for (int i = 0; i < cursor.getCount() && cursor.isCacheEnabled() && i < 100; i++) { 67 | cursor.moveToPosition(i); 68 | T inflate = cursor.inflate(); 69 | } 70 | 71 | return cursor; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/QuantumFluxBatchDispatcher.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.content.ContentProviderClient; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.net.Uri; 7 | import android.os.RemoteException; 8 | 9 | import java.util.ArrayList; 10 | 11 | import me.himanshusoni.quantumflux.QuantumFlux; 12 | import me.himanshusoni.quantumflux.QuantumFluxSyncHelper; 13 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 14 | 15 | public class QuantumFluxBatchDispatcher extends ArrayList { 16 | 17 | private final Context mContext; 18 | private final Class mInsertObject; 19 | private final int mDispatchSize; 20 | private final Uri mUri; 21 | private final TableDetails mTableDetails; 22 | 23 | private ContentProviderClient mContentProviderClient; 24 | private boolean isSync; 25 | private boolean mReleaseProvider; 26 | 27 | public QuantumFluxBatchDispatcher(Context context, Class insertObject, int dispatchSize) { 28 | this.mContext = context; 29 | this.mInsertObject = insertObject; 30 | this.mDispatchSize = dispatchSize; 31 | this.mUri = QuantumFlux.getItemUri(insertObject); 32 | this.mTableDetails = QuantumFlux.findTableDetails(insertObject); 33 | 34 | ensureCapacity(dispatchSize); 35 | } 36 | 37 | public QuantumFluxBatchDispatcher(Context context, ContentProviderClient provider, Class insertObject, boolean isSync, int dispatchSize) { 38 | this(context, insertObject, dispatchSize); 39 | this.mContentProviderClient = provider; 40 | this.isSync = isSync; 41 | mReleaseProvider = false; 42 | } 43 | 44 | @Override 45 | public boolean add(T object) { 46 | checkSizeAndDispatch(); 47 | return super.add(object); 48 | } 49 | 50 | private void checkSizeAndDispatch() { 51 | if (size() >= mDispatchSize) dispatch(); 52 | } 53 | 54 | public void dispatch() { 55 | if (isEmpty()) return; 56 | 57 | if (mContentProviderClient == null) { 58 | this.mContentProviderClient = mContext.getContentResolver().acquireContentProviderClient(mUri); 59 | mReleaseProvider = true; 60 | } 61 | 62 | try { 63 | if (isSync) { 64 | QuantumFluxSyncHelper.insert(mContentProviderClient, toArray()); 65 | } else { 66 | ContentValues[] values = ModelInflater.deflateAll(mTableDetails, toArray()); 67 | mContentProviderClient.bulkInsert(mUri, values); 68 | } 69 | clear(); 70 | } catch (RemoteException e) { 71 | release(false); 72 | throw new QuantumFluxException("Failed to insert objects", e); 73 | } 74 | } 75 | 76 | public void release(boolean dispatchRemaining) { 77 | if (dispatchRemaining) dispatch(); 78 | 79 | clear(); 80 | 81 | if (mReleaseProvider) mContentProviderClient.release(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/QuantumFluxLoader.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader; 2 | 3 | import android.content.Context; 4 | import android.content.CursorLoader; 5 | 6 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 7 | import me.himanshusoni.quantumflux.model.query.Select; 8 | import me.himanshusoni.quantumflux.model.util.ContentResolverValues; 9 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursor; 10 | 11 | /** 12 | * A loaded implementation that will create a new Cursor loaded based on the select statement provided. 13 | */ 14 | public class QuantumFluxLoader extends CursorLoader { 15 | 16 | private TableDetails mTableDetails; 17 | private int mCacheSize = 0; 18 | 19 | /** 20 | * Creates a new cursor loader using the select statement provided. The default implementation 21 | * will enable the cache of the cursor to improve view performance. To manually specify the 22 | * cursor cache size, use the overloaded constructor. 23 | * 24 | * @param context The context that will be used to create the cursor. 25 | * @param select The select statement that will be used to retrieve the data. 26 | */ 27 | public QuantumFluxLoader(Context context, Select select) { 28 | super(context); 29 | 30 | ContentResolverValues resolverValues = select.asContentResolverValue(); 31 | setUri(resolverValues.getItemUri()); 32 | setProjection(resolverValues.getProjection()); 33 | setSelection(resolverValues.getWhere()); 34 | setSelectionArgs(resolverValues.getWhereArgs()); 35 | setSortOrder(resolverValues.getSortOrder()); 36 | 37 | mTableDetails = resolverValues.getTableDetails(); 38 | } 39 | 40 | /** 41 | * Creates a new cursor loader using the select statement provided. You 42 | * can specify the cache size to use, or use -1 to disable cursor caching. 43 | * 44 | * @param context The context that will be used to create the cursor. 45 | * @param select The select statement that will be used to retrieve the data. 46 | * @param cacheSize The cache size for the cursor, or -1 to disable caching 47 | */ 48 | public QuantumFluxLoader(Context context, Select select, int cacheSize) { 49 | this(context, select); 50 | 51 | enableCursorCache(cacheSize); 52 | } 53 | 54 | public void enableCursorCache(int size) { 55 | mCacheSize = size; 56 | } 57 | 58 | @Override 59 | public QuantumFluxCursor loadInBackground() { 60 | QuantumFluxCursor cursor = new QuantumFluxCursor<>(mTableDetails, super.loadInBackground()); 61 | 62 | if (mCacheSize == 0) { 63 | cursor.enableCache(); 64 | } else if (mCacheSize > 0) { 65 | cursor.enableCache(mCacheSize); 66 | } 67 | 68 | //Prefetch at least some items in preparation for the list 69 | for (int i = 0; i < cursor.getCount() && cursor.isCacheEnabled() && i < 100; i++) { 70 | cursor.moveToPosition(i); 71 | T inflate = cursor.inflate(); 72 | } 73 | 74 | return cursor; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/map/SqlColumnMappingFactory.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.map; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import me.himanshusoni.quantumflux.model.map.types.BigDecimalType; 8 | import me.himanshusoni.quantumflux.model.map.types.BooleanType; 9 | import me.himanshusoni.quantumflux.model.map.types.CalendarType; 10 | import me.himanshusoni.quantumflux.model.map.types.DateType; 11 | import me.himanshusoni.quantumflux.model.map.types.DoubleType; 12 | import me.himanshusoni.quantumflux.model.map.types.FloatType; 13 | import me.himanshusoni.quantumflux.model.map.types.IntegerType; 14 | import me.himanshusoni.quantumflux.model.map.types.LongType; 15 | import me.himanshusoni.quantumflux.model.map.types.ShortType; 16 | import me.himanshusoni.quantumflux.model.map.types.StringType; 17 | import me.himanshusoni.quantumflux.model.map.types.UUIDType; 18 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 19 | 20 | /** 21 | * The factory that will contain all of the available column conversion for the system. 22 | * This class can be extended, and the class name provided as part of the meta information to load the 23 | * extended class instead. 24 | */ 25 | public class SqlColumnMappingFactory { 26 | 27 | private final List mColumnMappings; 28 | 29 | public SqlColumnMappingFactory() { 30 | mColumnMappings = new ArrayList(); 31 | mColumnMappings.add(new BigDecimalType()); 32 | mColumnMappings.add(new BooleanType()); 33 | mColumnMappings.add(new CalendarType()); 34 | mColumnMappings.add(new DateType()); 35 | mColumnMappings.add(new DoubleType()); 36 | mColumnMappings.add(new FloatType()); 37 | mColumnMappings.add(new IntegerType()); 38 | mColumnMappings.add(new LongType()); 39 | mColumnMappings.add(new ShortType()); 40 | mColumnMappings.add(new StringType()); 41 | mColumnMappings.add(new UUIDType()); 42 | } 43 | 44 | public void addColumnMapping(SqlColumnMapping mapping) { 45 | mColumnMappings.add(mapping); 46 | } 47 | 48 | public SqlColumnMapping findColumnMapping(Class fieldType) { 49 | Class fieldTypeWrapped = wrapPrimitives(fieldType); 50 | 51 | for (SqlColumnMapping columnMapping : mColumnMappings) { 52 | Class columnType = columnMapping.getJavaType(); 53 | if (columnType.equals(fieldTypeWrapped) || columnType.isAssignableFrom(fieldType)) 54 | return columnMapping; 55 | } 56 | 57 | throw new QuantumFluxException("No valid SQL mapping found for type " + fieldType); 58 | } 59 | 60 | private Class wrapPrimitives(Class fieldType) { 61 | 62 | if (!fieldType.isPrimitive()) return fieldType; 63 | 64 | if (long.class.equals(fieldType)) return Long.class; 65 | if (int.class.equals(fieldType)) return Integer.class; 66 | if (double.class.equals(fieldType)) return Double.class; 67 | if (float.class.equals(fieldType)) return Float.class; 68 | if (short.class.equals(fieldType)) return Short.class; 69 | if (boolean.class.equals(fieldType)) return Boolean.class; 70 | if (byte.class.equals(fieldType)) return Byte.class; 71 | if (void.class.equals(fieldType)) return Void.class; 72 | if (char.class.equals(fieldType)) return Character.class; 73 | 74 | throw new QuantumFluxException("No primitive type registered for type " + fieldType); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/QuantumFluxCursor.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.database.Cursor; 4 | import android.database.CursorWrapper; 5 | import android.util.LruCache; 6 | 7 | import java.lang.ref.SoftReference; 8 | 9 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 10 | 11 | /** 12 | * This class is a wrapper for cursor returned by the ORM, it has some helper methods like inflating to an object from the cursor 13 | */ 14 | public class QuantumFluxCursor extends CursorWrapper { 15 | 16 | private final TableDetails mTableDetails; 17 | private LruCache> mObjectCache; 18 | 19 | public QuantumFluxCursor(TableDetails tableDetails, Cursor cursor) { 20 | super(cursor); 21 | this.mTableDetails = tableDetails; 22 | } 23 | 24 | public QuantumFluxCursor(TableDetails tableDetails, Cursor cursor, int cacheSize) { 25 | this(tableDetails, cursor); 26 | enableCache(cacheSize); 27 | } 28 | 29 | /** 30 | * Uses an LRU cache to store some of the objects returned by this cursor. This can be help full if some objects 31 | * contain lazy initialized values, and it improves performance. 32 | * 33 | * @param size The size of the cache to create 34 | */ 35 | public void enableCache(int size) { 36 | if (size > 0) 37 | this.mObjectCache = new LruCache<>(size); 38 | } 39 | 40 | /** 41 | * Initializes the cache with the cursor's count. The cached values are 42 | * stored using soft references, and should not cause any memory issues, 43 | * but setting the size is the preferred way of enabling the cache. 44 | * 45 | * @see #enableCache(int) 46 | */ 47 | public void enableCache() { 48 | enableCache(getCount()); 49 | } 50 | 51 | /** 52 | * Inflates an object at the current cursor position. If the cache is enabled, and the object exists in the 53 | * cache, then that object wil be returned, otherwise it is inflated and added to the cache before returning. 54 | * 55 | * @return The inflated object. 56 | */ 57 | public T inflate() { 58 | return getObjectFromCacheOrInflate(); 59 | } 60 | 61 | /** 62 | * @return The table details that is used to construct the object 63 | */ 64 | public TableDetails getTableDetails() { 65 | return mTableDetails; 66 | } 67 | 68 | /** 69 | * Attempts to retrieve an object from the cache, if it does not exist in the cache, and the cache is enabled, then 70 | * the object will be inflated and added to cache before returning. 71 | * 72 | * @return The inflated object 73 | */ 74 | private T getObjectFromCacheOrInflate() { 75 | 76 | if (mObjectCache == null) return ModelInflater.inflate(this, mTableDetails); 77 | 78 | SoftReference objectReference = mObjectCache.get(getPosition()); 79 | 80 | T cachedObject = null; 81 | if (objectReference != null) cachedObject = objectReference.get(); 82 | 83 | if (cachedObject == null) cachedObject = insertCacheObject(); 84 | 85 | return cachedObject; 86 | } 87 | 88 | public boolean isCacheEnabled() { 89 | return mObjectCache != null; 90 | } 91 | 92 | /** 93 | * Inflates the object at the current cursor position, and inserts it into the cache with the position as ID 94 | * 95 | * @return The inflated object 96 | */ 97 | private T insertCacheObject() { 98 | T cachedObject = ModelInflater.inflate(this, mTableDetails); 99 | mObjectCache.put(getPosition(), new SoftReference<>(cachedObject)); 100 | 101 | return cachedObject; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/ManifestHelper.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageManager; 6 | import android.text.TextUtils; 7 | 8 | import me.himanshusoni.quantumflux.logger.QuantumFluxLog; 9 | 10 | public class ManifestHelper { 11 | 12 | public static final String METADATA_AUTHORITY = "AUTHORITY"; 13 | 14 | public final static String METADATA_DATABASE_NAME = "DATABASE_NAME"; 15 | public final static String METADATA_VERSION = "DATABASE_VERSION"; 16 | public final static String METADATA_PACKAGE_NAME = "PACKAGE_NAME"; 17 | public final static String METADATA_QUERY_LOG = "QUERY_LOG"; 18 | 19 | public static final String DATABASE_DEFAULT_NAME = "QuantumFlux.db"; 20 | 21 | private static String authority; 22 | private static String databaseName; 23 | private static String packageName; 24 | private static int databaseVersion; 25 | 26 | public static String getAuthority(Context context) { 27 | if (authority == null) { 28 | authority = getMetaDataString(context, METADATA_AUTHORITY); 29 | if (TextUtils.isEmpty(authority)) 30 | throw new QuantumFluxException("AUTHORITY must be provided in meta data"); 31 | } 32 | 33 | return authority; 34 | } 35 | 36 | public static String getDatabaseName(Context context) { 37 | if (databaseName == null) { 38 | databaseName = getMetaDataString(context, METADATA_DATABASE_NAME); 39 | if (TextUtils.isEmpty(databaseName)) { 40 | QuantumFluxLog.d("DATABASE_NAME is not provided in meta data, using default name"); 41 | databaseName = DATABASE_DEFAULT_NAME; 42 | } 43 | } 44 | 45 | return databaseName; 46 | } 47 | 48 | public static String getPackageName(Context context) { 49 | if (packageName == null) { 50 | packageName = getMetaDataString(context, METADATA_PACKAGE_NAME); 51 | if (TextUtils.isEmpty(packageName)) 52 | throw new QuantumFluxException("PACKAGE_NAME must be provided in meta data"); 53 | } 54 | 55 | return packageName; 56 | } 57 | 58 | public static int getDatabaseVersion(Context context) { 59 | if (databaseVersion == 0) { 60 | databaseVersion = getMetaDataInteger(context, METADATA_VERSION); 61 | if (databaseVersion == 0) 62 | throw new QuantumFluxException("DATABASE_VERSION must be provided in meta data"); 63 | } 64 | 65 | return databaseVersion; 66 | } 67 | 68 | public static boolean isQueryLogEnabled(Context context) { 69 | return getMetaDataBoolean(context, METADATA_QUERY_LOG); 70 | } 71 | 72 | private static String getMetaDataString(Context context, String name) { 73 | String value = null; 74 | 75 | PackageManager pm = context.getPackageManager(); 76 | try { 77 | ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); 78 | value = ai.metaData.getString(name); 79 | } catch (Exception e) { 80 | QuantumFluxLog.d("Couldn't find config value: " + name); 81 | } 82 | 83 | return value; 84 | } 85 | 86 | private static Integer getMetaDataInteger(Context context, String name) { 87 | Integer value = null; 88 | 89 | PackageManager pm = context.getPackageManager(); 90 | try { 91 | ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); 92 | value = ai.metaData.getInt(name); 93 | } catch (Exception e) { 94 | QuantumFluxLog.d("Couldn't find config value: " + name); 95 | } 96 | 97 | return value; 98 | } 99 | 100 | private static Boolean getMetaDataBoolean(Context context, String name) { 101 | Boolean value = false; 102 | 103 | PackageManager pm = context.getPackageManager(); 104 | try { 105 | ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); 106 | value = ai.metaData.getBoolean(name); 107 | } catch (Exception e) { 108 | QuantumFluxLog.d("Couldn't find config value: " + name); 109 | } 110 | 111 | return value; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/util/ModelInflater.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.util; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | 6 | import java.util.List; 7 | 8 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 9 | 10 | /** 11 | * Handles the inflation and deflation of Java objects to and from content values/cursors 12 | */ 13 | public class ModelInflater { 14 | 15 | public static Object deflateColumn(TableDetails tableDetails, TableDetails.ColumnDetails columnDetails, Object dataModelObject) { 16 | try { 17 | Object value = columnDetails.getColumnField().get(dataModelObject); 18 | 19 | if (value == null) return null; 20 | else return columnDetails.getColumnTypeMapping().toSqlType(value); 21 | } catch (IllegalAccessException e) { 22 | throw new QuantumFluxException("Unable to access protected field, change the access level: " + columnDetails.getColumnName()); 23 | } 24 | } 25 | 26 | public static ContentValues deflate(TableDetails tableDetails, Object dataModelObject) { 27 | List columns = tableDetails.getColumns(); 28 | ContentValues contentValues = new ContentValues(columns.size()); 29 | 30 | for (int i = 0; i < columns.size(); i++) { 31 | TableDetails.ColumnDetails columnDetails = columns.get(i); 32 | 33 | if (columnDetails.isAutoIncrement()) continue; 34 | 35 | try { 36 | columnDetails.setContentValue(contentValues, dataModelObject); 37 | } catch (IllegalAccessException e) { 38 | throw new QuantumFluxException("Unable to access protected field, change the access level: " + columnDetails.getColumnName()); 39 | } 40 | } 41 | 42 | return contentValues; 43 | } 44 | 45 | 46 | public static ContentValues[] deflateAll(TableDetails tableDetails, Object... dataModelObjects) { 47 | List columns = tableDetails.getColumns(); 48 | ContentValues[] contentValuesArray = new ContentValues[dataModelObjects.length]; 49 | for (int i = 0; i < dataModelObjects.length; i++) { 50 | contentValuesArray[i] = new ContentValues(columns.size()); 51 | } 52 | 53 | for (int i = 0; i < columns.size(); i++) { 54 | TableDetails.ColumnDetails columnDetails = columns.get(i); 55 | 56 | if (columnDetails.isAutoIncrement()) continue; 57 | 58 | for (int j = 0; j < dataModelObjects.length; j++) { 59 | try { 60 | columnDetails.setContentValue(contentValuesArray[j], dataModelObjects[j]); 61 | } catch (IllegalAccessException e) { 62 | throw new QuantumFluxException("Unable to access protected field, change the access level: " + columnDetails.getColumnName()); 63 | } 64 | } 65 | } 66 | return contentValuesArray; 67 | } 68 | 69 | public static T inflate(Cursor cursor, TableDetails tableDetails) { 70 | T dataModelObject; 71 | 72 | try { 73 | dataModelObject = (T) tableDetails.createNewModelInstance(); 74 | } catch (Exception ex) { 75 | throw new QuantumFluxException("Could not create a new instance of data model object: " + tableDetails.getTableName()); 76 | } 77 | 78 | for (int i = 0; i < cursor.getColumnCount(); i++) { 79 | String columnName = cursor.getColumnName(i); 80 | TableDetails.ColumnDetails columnDetails = tableDetails.findColumn(columnName); 81 | inflateColumn(cursor, dataModelObject, columnDetails, i); 82 | 83 | } 84 | 85 | return dataModelObject; 86 | } 87 | 88 | private static void inflateColumn(Cursor cursor, T dataModelObject, TableDetails.ColumnDetails columnDetails, int columnIndex) { 89 | 90 | //If the column details is not required, then check if it is null 91 | if (!columnDetails.isRequired() && cursor.isNull(columnIndex)) { 92 | return; 93 | } 94 | 95 | try { 96 | columnDetails.setFieldValue(cursor, columnIndex, dataModelObject); 97 | } catch (IllegalAccessException e) { 98 | throw new QuantumFluxException("Not allowed to alter the value of the field, please change the access level: " + columnDetails.getColumnName()); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/QuantumFluxAsyncCursorAdaptor.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | import android.view.View; 8 | 9 | import java.lang.ref.SoftReference; 10 | import java.util.LinkedList; 11 | import java.util.Queue; 12 | import java.util.concurrent.locks.Condition; 13 | import java.util.concurrent.locks.Lock; 14 | import java.util.concurrent.locks.ReentrantLock; 15 | 16 | import me.himanshusoni.quantumflux.logger.QuantumFluxLog; 17 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursor; 18 | 19 | public abstract class QuantumFluxAsyncCursorAdaptor extends QuantumFluxCursorAdaptor { 20 | 21 | private final Lock mLock = new ReentrantLock(); 22 | private final Condition mCondition = mLock.newCondition(); 23 | private final Queue mLoaderQueue = new LinkedList(); 24 | private final LoaderThreadHandler mLoaderThreadHandler = new LoaderThreadHandler(this); 25 | private LoaderThread mLoaderThread; 26 | 27 | public QuantumFluxAsyncCursorAdaptor(Context context, int layoutId) { 28 | super(context, layoutId); 29 | } 30 | 31 | public QuantumFluxAsyncCursorAdaptor(Context context, Cursor c, int layoutId) { 32 | super(context, c, layoutId); 33 | } 34 | 35 | public QuantumFluxAsyncCursorAdaptor(Context context, Cursor c, int layoutId, int flags) { 36 | super(context, c, layoutId, flags); 37 | } 38 | 39 | @Override 40 | public void changeCursor(Cursor cursor) { 41 | dispose(); 42 | super.changeCursor(cursor); 43 | } 44 | 45 | @Override 46 | public void bindView(View view, Context context, Cursor cursor) { 47 | startLoaderThreadIfStopped(); 48 | mLock.lock(); 49 | T inflate = ((QuantumFluxCursor) getCursor()).inflate(); 50 | if (!mLoaderQueue.contains(inflate)) 51 | mLoaderQueue.offer(inflate); 52 | mCondition.signal(); 53 | mLock.unlock(); 54 | super.bindView(view, context, cursor); 55 | } 56 | 57 | private void dispose() { 58 | mLock.lock(); 59 | if (mLoaderThread != null) { 60 | mLoaderThread.isCancelled = true; 61 | mLoaderThread = null; 62 | } 63 | mCondition.signal(); 64 | mLock.unlock(); 65 | } 66 | 67 | private void startLoaderThreadIfStopped() { 68 | if (mLoaderThread == null) { 69 | mLoaderThread = new LoaderThread(); 70 | mLoaderThread.start(); 71 | } 72 | } 73 | 74 | public abstract boolean loadAsyncInformation(T information); 75 | 76 | private class LoaderThread extends Thread { 77 | volatile boolean isCancelled = false; 78 | QuantumFluxCursor cursor = (QuantumFluxCursor) getCursor(); 79 | 80 | @Override 81 | public void run() { 82 | 83 | setName(QuantumFluxAsyncCursorAdaptor.class.getSimpleName() + "_Loader"); 84 | try { 85 | while (isStillValid()) { 86 | 87 | mLock.lock(); 88 | while (mLoaderQueue.peek() == null && isStillValid()) { 89 | mCondition.await(); 90 | } 91 | 92 | if (!isStillValid()) { 93 | mLock.unlock(); 94 | break; 95 | } 96 | 97 | T item = mLoaderQueue.poll(); 98 | mLock.unlock(); 99 | if (loadAsyncInformation(item)) 100 | mLoaderThreadHandler.sendEmptyMessage(0); 101 | } 102 | } catch (InterruptedException e) { 103 | QuantumFluxLog.e("Failed while waiting for items", e); 104 | } 105 | } 106 | 107 | private boolean isStillValid() { 108 | return cursor != null && !cursor.isClosed() && !isCancelled; 109 | } 110 | } 111 | 112 | private static class LoaderThreadHandler extends Handler { 113 | 114 | private final SoftReference adaptorReference; 115 | 116 | LoaderThreadHandler(QuantumFluxCursorAdaptor adaptor) { 117 | this.adaptorReference = new SoftReference(adaptor); 118 | } 119 | 120 | @Override 121 | public void handleMessage(Message msg) { 122 | super.handleMessage(msg); 123 | 124 | QuantumFluxCursorAdaptor adaptor = adaptorReference.get(); 125 | if (adaptor != null) adaptor.notifyDataSetChanged(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/loader/support/QuantumFluxAsyncCursorAdaptor.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.loader.support; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | import android.view.View; 8 | 9 | import java.lang.ref.SoftReference; 10 | import java.util.LinkedList; 11 | import java.util.Queue; 12 | import java.util.concurrent.locks.Condition; 13 | import java.util.concurrent.locks.Lock; 14 | import java.util.concurrent.locks.ReentrantLock; 15 | 16 | import me.himanshusoni.quantumflux.logger.QuantumFluxLog; 17 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursor; 18 | 19 | public abstract class QuantumFluxAsyncCursorAdaptor extends QuantumFluxCursorAdaptor { 20 | 21 | private final Lock mLock = new ReentrantLock(); 22 | private final Condition mCondition = mLock.newCondition(); 23 | private final Queue mLoaderQueue = new LinkedList(); 24 | private final LoaderThreadHandler mLoaderThreadHandler = new LoaderThreadHandler(this); 25 | private LoaderThread mLoaderThread; 26 | 27 | public QuantumFluxAsyncCursorAdaptor(Context context, int layoutId) { 28 | super(context, layoutId); 29 | } 30 | 31 | public QuantumFluxAsyncCursorAdaptor(Context context, Cursor c, int layoutId) { 32 | super(context, c, layoutId); 33 | } 34 | 35 | public QuantumFluxAsyncCursorAdaptor(Context context, Cursor c, int layoutId, int flags) { 36 | super(context, c, layoutId, flags); 37 | } 38 | 39 | @Override 40 | public void changeCursor(Cursor cursor) { 41 | dispose(); 42 | super.changeCursor(cursor); 43 | } 44 | 45 | @Override 46 | public void bindView(View view, Context context, Cursor cursor) { 47 | startLoaderThreadIfStopped(); 48 | mLock.lock(); 49 | T inflate = ((QuantumFluxCursor) getCursor()).inflate(); 50 | if (!mLoaderQueue.contains(inflate)) 51 | mLoaderQueue.offer(inflate); 52 | mCondition.signal(); 53 | mLock.unlock(); 54 | super.bindView(view, context, cursor); 55 | } 56 | 57 | private void dispose() { 58 | mLock.lock(); 59 | if (mLoaderThread != null) { 60 | mLoaderThread.isCancelled = true; 61 | mLoaderThread = null; 62 | } 63 | mCondition.signal(); 64 | mLock.unlock(); 65 | } 66 | 67 | private void startLoaderThreadIfStopped() { 68 | if (mLoaderThread == null) { 69 | mLoaderThread = new LoaderThread(); 70 | mLoaderThread.start(); 71 | } 72 | } 73 | 74 | public abstract boolean loadAsyncInformation(T information); 75 | 76 | private class LoaderThread extends Thread { 77 | volatile boolean isCancelled = false; 78 | QuantumFluxCursor mCursor = (QuantumFluxCursor) getCursor(); 79 | 80 | @Override 81 | public void run() { 82 | 83 | setName(QuantumFluxAsyncCursorAdaptor.class.getSimpleName() + "_Loader"); 84 | try { 85 | while (isStillValid()) { 86 | 87 | mLock.lock(); 88 | while (mLoaderQueue.peek() == null && isStillValid()) { 89 | mCondition.await(); 90 | } 91 | 92 | if (!isStillValid()) { 93 | mLock.unlock(); 94 | break; 95 | } 96 | 97 | T item = mLoaderQueue.poll(); 98 | mLock.unlock(); 99 | if (loadAsyncInformation(item)) 100 | mLoaderThreadHandler.sendEmptyMessage(0); 101 | } 102 | } catch (InterruptedException e) { 103 | QuantumFluxLog.e("Failed while waiting for items", e); 104 | } 105 | } 106 | 107 | private boolean isStillValid() { 108 | return mCursor != null && !mCursor.isClosed() && !isCancelled; 109 | } 110 | } 111 | 112 | private static class LoaderThreadHandler extends Handler { 113 | 114 | private final SoftReference adaptorReference; 115 | 116 | LoaderThreadHandler(QuantumFluxCursorAdaptor adaptor) { 117 | this.adaptorReference = new SoftReference(adaptor); 118 | } 119 | 120 | @Override 121 | public void handleMessage(Message msg) { 122 | super.handleMessage(msg); 123 | 124 | QuantumFluxCursorAdaptor adaptor = adaptorReference.get(); 125 | if (adaptor != null) adaptor.notifyDataSetChanged(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuantumFlux 2 | A Powerful Android Content Provider ORM. It uses Content Providers to access the data and internally uses SQLite to actually store the data. 3 | It can be used as a typical ORM, by creating Java objects and applying some Annotations and/or by extending `QuantumFluxRecord` on them and 4 | they will be accessed normally and creates [ContentProvider](http://developer.android.com/reference/android/content/ContentProvider.html). 5 | 6 | 7 | [![Download](https://api.bintray.com/packages/himanshu-soni/maven/quantum-flux/images/download.svg) ](https://bintray.com/himanshu-soni/maven/quantum-flux/_latestVersion) 8 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-QuantumFlux-green.svg?style=flat)](https://android-arsenal.com/details/1/2347) 9 | 10 | 11 | For more information in detail, please check [Wiki](https://github.com/himanshu-soni/QuantumFlux/wiki) page. 12 | 13 | ## Features 14 | 15 | 1. Create table from data modal objects, either by annotation or by extending `QuantumFluxRecord`. 16 | 2. Create table view from data modal. 17 | 3. Advanced query creation using `QueryBuilder`, complex queries can be created very easily. 18 | 4. Supports many common data types as Date, Calender, UUID, BigDecimal, and also allows custom column type mappings. 19 | 5. Table change listeners for views to auto reflect data changes. 20 | 6. Very easy to setup, use and query. 21 | 22 | ## Installation 23 | 24 | #### Gradle (Recommended) 25 | 26 | add following line to your module's dependency list: 27 | 28 | ``` 29 | compile 'me.himanshusoni:quantum-flux:0.9.3' 30 | ``` 31 | 32 | 33 | #### Maven 34 | 35 | Declare the dependency in Maven: 36 | 37 | ``` 38 | 39 | me.himanshusoni 40 | quantum-flux 41 | 0.9.3 42 | pom 43 | 44 | ``` 45 | 46 | #### For eclipse users 47 | 48 | You can also download the source and import as library project. 49 | 50 | #### or add jar 51 | 52 | See [releases](https://github.com/himanshu-soni/QuantumFlux/releases) page to download jar for latest version directly. 53 | 54 | =================== 55 | 56 | ## Basic Usage 57 | 58 | 1. Install the project via any of the method above. and extend `Application` class and use it as following: 59 | 60 | ``` 61 | public class SampleApplication extends Application { 62 | @Override 63 | protected void attachBaseContext(Context base) { 64 | super.attachBaseContext(base); 65 | QuantumFlux.initialize(this); 66 | } 67 | } 68 | ``` 69 | 70 | 2. register this application class in Manifest file: 71 | 72 | ```xml 73 | 77 | ``` 78 | 79 | 80 | 3. add following meta data in `` tag 81 | 82 | ```xml 83 | 84 | 85 | 86 | 87 | 88 | ``` 89 | 90 | 91 | 4. register content provider: 92 | 93 | ```xml 94 | 98 | ``` 99 | 100 | 5. Now, you just have to create modal classes by: 101 | * annotation: 102 | 103 | ``` 104 | @Table 105 | public class Book { 106 | ... 107 | } 108 | ``` 109 | 110 | * extending class: 111 | 112 | ``` 113 | public class Author extends QuantumFluxRecord { 114 | public String name; 115 | ... 116 | } 117 | ``` 118 | 119 | 6. Access: 120 | 121 | ``` 122 | Book book = new Book(); 123 | book.name = "Sorcerer's Stone"; 124 | book.isbn = "122342564"; 125 | QuantumFlux.insert(book); 126 | ``` 127 | 128 | ``` 129 | Author author = new Author(); 130 | author.name = "J.K. Rollings"; 131 | author.save(); 132 | ``` 133 | 134 | ``` 135 | Author first = Select.from(Author.class).first(); 136 | first.name = "J. K. Rowling"; 137 | first.update(); 138 | ``` 139 | 140 | ``` 141 | QuantumFlux.deleteAll(Book.class); 142 | Author first = Select.from(Author.class).first(); 143 | first.delete(); 144 | ``` 145 | 146 | ========================= 147 | 148 | For detailed configuration and advance usages, go through 149 | [https://github.com/himanshu-soni/QuantumFlux/wiki](https://github.com/himanshu-soni/QuantumFlux/wiki) 150 | 151 | Inspired from https://github.com/wackymax/CPOrm and https://github.com/satyan/sugar 152 | 153 | Special thanks to Satya Narayan (@satyan) and Hennie Brink (@Wackymax). 154 | 155 | developed to make programming easy. 156 | 157 | by Himanshu Soni (himanshu_soni@mail.com) 158 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/QuantumFluxDatabase.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.database.sqlite.SQLiteDatabase; 6 | import android.database.sqlite.SQLiteOpenHelper; 7 | import android.os.Build; 8 | 9 | import me.himanshusoni.quantumflux.logger.QuantumFluxLog; 10 | import me.himanshusoni.quantumflux.model.generate.ReflectionHelper; 11 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 12 | import me.himanshusoni.quantumflux.model.generate.TableGenerator; 13 | import me.himanshusoni.quantumflux.model.generate.TableView; 14 | import me.himanshusoni.quantumflux.model.generate.TableViewGenerator; 15 | import me.himanshusoni.quantumflux.model.util.ManifestHelper; 16 | import me.himanshusoni.quantumflux.model.util.QuantumFluxCursorFactory; 17 | import me.himanshusoni.quantumflux.model.util.TableDetailsCache; 18 | 19 | import static me.himanshusoni.quantumflux.model.generate.ReflectionHelper.getDomainClasses; 20 | 21 | /** 22 | * Handles the creation of the database and all of its objects 23 | */ 24 | public class QuantumFluxDatabase extends SQLiteOpenHelper { 25 | 26 | private final Context mContext; 27 | private final TableDetailsCache mTableDetailsCache; 28 | private final boolean isQueryLoggingEnabled; 29 | private QuantumFluxDatabaseUpgradeListener mQuantumFluxDatabaseUpgradeListener; 30 | 31 | public QuantumFluxDatabase(Context context, QuantumFluxDatabaseUpgradeListener quantumFluxDatabaseUpgradeListener) { 32 | super(context, ManifestHelper.getDatabaseName(context), new QuantumFluxCursorFactory(ManifestHelper.isQueryLogEnabled(context)), ManifestHelper.getDatabaseVersion(context)); 33 | this.mContext = context; 34 | this.mTableDetailsCache = new TableDetailsCache(); 35 | this.mTableDetailsCache.init(context, getDomainClasses(context, ReflectionHelper.TableType.TABLE)); 36 | this.isQueryLoggingEnabled = ManifestHelper.isQueryLogEnabled(context); 37 | this.mQuantumFluxDatabaseUpgradeListener = quantumFluxDatabaseUpgradeListener; 38 | } 39 | 40 | @Override 41 | public void onCreate(SQLiteDatabase sqLiteDatabase) { 42 | for (Class dataModelObject : getDomainClasses(mContext, ReflectionHelper.TableType.TABLE)) { 43 | String createStatement = TableGenerator.generateTableCreate(findTableDetails(dataModelObject), false); 44 | if (isQueryLoggingEnabled) { 45 | QuantumFluxLog.d("Creating Table: " + createStatement); 46 | } 47 | sqLiteDatabase.execSQL(createStatement); 48 | } 49 | 50 | for (Class dataModelObject : getDomainClasses(mContext, ReflectionHelper.TableType.TABLE_VIEW)) { 51 | String createStatement = TableViewGenerator.createViewStatement(findTableDetails(dataModelObject), (Class) dataModelObject); 52 | if (isQueryLoggingEnabled) { 53 | QuantumFluxLog.d("Creating View: " + createStatement); 54 | } 55 | sqLiteDatabase.execSQL(createStatement); 56 | } 57 | } 58 | 59 | @Override 60 | public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { 61 | 62 | for (Class dataModelObject : getDomainClasses(mContext, ReflectionHelper.TableType.TABLE)) { 63 | String createStatement = TableGenerator.generateTableDrop(findTableDetails(dataModelObject), false); 64 | if (isQueryLoggingEnabled) { 65 | QuantumFluxLog.d("Dropping Table: " + createStatement); 66 | } 67 | sqLiteDatabase.execSQL(createStatement); 68 | } 69 | 70 | for (Class dataModelObject : getDomainClasses(mContext, ReflectionHelper.TableType.TABLE_VIEW)) { 71 | String createStatement = TableViewGenerator.createDropViewStatement(findTableDetails(dataModelObject)); 72 | if (isQueryLoggingEnabled) { 73 | QuantumFluxLog.d("Dropping View: " + createStatement); 74 | } 75 | sqLiteDatabase.execSQL(createStatement); 76 | } 77 | 78 | if (mQuantumFluxDatabaseUpgradeListener != null) { 79 | mQuantumFluxDatabaseUpgradeListener.onDatabaseUpgraded(oldVersion, newVersion); 80 | } 81 | 82 | onCreate(sqLiteDatabase); 83 | } 84 | 85 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 86 | @Override 87 | public void onConfigure(SQLiteDatabase db) { 88 | super.onConfigure(db); 89 | if (!db.isReadOnly()) { 90 | db.enableWriteAheadLogging(); 91 | } 92 | } 93 | 94 | @Override 95 | public void onOpen(SQLiteDatabase db) { 96 | super.onOpen(db); 97 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { 98 | db.enableWriteAheadLogging(); 99 | } 100 | } 101 | 102 | private TableDetails findTableDetails(Class object) { 103 | return mTableDetailsCache.findTableDetails(mContext, object); 104 | } 105 | 106 | /** 107 | * Returns the table details cache that can be used to lookup table details for java objects. This 108 | * should be used instead of {@link ReflectionHelper}, so that we do not 109 | * try to do reflection to much 110 | * 111 | * @return The table details cache object that can be used to obtain table details 112 | */ 113 | public TableDetailsCache getTableDetailsCache() { 114 | return mTableDetailsCache; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/generate/TableGenerator.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.generate; 2 | 3 | 4 | import java.util.Arrays; 5 | import java.util.Iterator; 6 | 7 | import me.himanshusoni.quantumflux.model.annotation.Index; 8 | import me.himanshusoni.quantumflux.model.annotation.TableConstraint; 9 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 10 | 11 | /** 12 | * The java object will be converted to a table details object and the supplied information is then used to generate the 13 | * statements. 14 | */ 15 | public class TableGenerator { 16 | 17 | public static String generateTableDrop(TableDetails tableDetails, boolean prettyPrint) { 18 | StringBuilder tableQuery = new StringBuilder(); 19 | 20 | prettyPrint(0, prettyPrint, tableQuery); 21 | tableQuery.append("DROP TABLE IF EXISTS "); 22 | tableQuery.append(tableDetails.getTableName()); 23 | tableQuery.append(";"); 24 | 25 | return tableQuery.toString(); 26 | } 27 | 28 | public static String generateTableCreate(TableDetails tableDetails, boolean prettyPrint) { 29 | StringBuilder tableQuery = new StringBuilder(); 30 | 31 | prettyPrint(0, prettyPrint, tableQuery); 32 | tableQuery.append("CREATE TABLE "); 33 | tableQuery.append(tableDetails.getTableName()); 34 | 35 | prettyPrint(1, prettyPrint, tableQuery); 36 | tableQuery.append("("); 37 | prettyPrint(1, prettyPrint, tableQuery); 38 | 39 | Iterator columnIterator = tableDetails.getColumns().iterator(); 40 | while (columnIterator.hasNext()) { 41 | TableDetails.ColumnDetails columnDetails = columnIterator.next(); 42 | 43 | prettyPrint(2, prettyPrint, tableQuery); 44 | tableQuery.append(createColumnDefinition(columnDetails)); 45 | 46 | if (columnIterator.hasNext()) tableQuery.append(", "); 47 | } 48 | Iterator tableConstraintIterator = tableDetails.getConstraints().iterator(); 49 | while (tableConstraintIterator.hasNext()) { 50 | TableConstraint tableConstraint = tableConstraintIterator.next(); 51 | 52 | prettyPrint(3, prettyPrint, tableQuery); 53 | tableQuery.append(createTableConstraint(tableConstraint)); 54 | 55 | if (tableConstraintIterator.hasNext()) tableQuery.append(","); 56 | } 57 | 58 | prettyPrint(1, prettyPrint, tableQuery); 59 | tableQuery.append(");"); 60 | prettyPrint(1, prettyPrint, tableQuery); 61 | 62 | for (Index index : tableDetails.getIndices()) { 63 | 64 | prettyPrint(1, prettyPrint, tableQuery); 65 | tableQuery.append("CREATE INDEX "); 66 | tableQuery.append(index.indexName()); 67 | tableQuery.append(" ON "); 68 | tableQuery.append(tableDetails.getTableName()); 69 | tableQuery.append(" ("); 70 | 71 | for (int i = 0; i < index.indexColumns().length; i++) { 72 | 73 | String column = index.indexColumns()[i]; 74 | tableQuery.append(column); 75 | 76 | if ((i + 1) < index.indexColumns().length) tableQuery.append(", "); 77 | } 78 | tableQuery.append(");"); 79 | prettyPrint(1, prettyPrint, tableQuery); 80 | } 81 | 82 | return tableQuery.toString(); 83 | } 84 | 85 | private static void prettyPrint(int tabSpace, boolean prettyPrint, StringBuilder tableQuery) { 86 | if (prettyPrint) { 87 | tableQuery.append("\n"); 88 | 89 | for (int i = 0; i < tabSpace; i++) { 90 | tableQuery.append("\t"); 91 | } 92 | } 93 | } 94 | 95 | private static StringBuilder createColumnDefinition(TableDetails.ColumnDetails columnDetails) { 96 | 97 | StringBuilder columnDefinition = new StringBuilder(); 98 | columnDefinition.append(columnDetails.getColumnName()); 99 | columnDefinition.append(" "); 100 | columnDefinition.append(columnDetails.getColumnTypeMapping().getSqlColumnTypeName()); 101 | 102 | if (columnDetails.isPrimaryKey()) { 103 | columnDefinition.append(" PRIMARY KEY"); 104 | if (columnDetails.isAutoIncrement()) columnDefinition.append(" AUTOINCREMENT"); 105 | } else if (columnDetails.isUnique()) columnDefinition.append(" UNIQUE"); 106 | else if (columnDetails.isRequired()) columnDefinition.append(" NOT NULL"); 107 | 108 | return columnDefinition; 109 | } 110 | 111 | 112 | private static StringBuilder createTableConstraint(TableConstraint constraint) { 113 | StringBuilder constraintDef = new StringBuilder(); 114 | constraintDef.append("CONSTRAINT "); 115 | constraintDef.append(constraint.name()); 116 | 117 | switch (constraint.constraintType()) { 118 | case PRIMARY_KEY: 119 | constraintDef.append(" PRIMARY KEY "); 120 | break; 121 | case UNIQUE: 122 | constraintDef.append(" UNIQUE "); 123 | break; 124 | default: 125 | throw new QuantumFluxException("Constraint Type not supported: " + constraint.constraintType()); 126 | } 127 | 128 | Iterator columnIterator = Arrays.asList(constraint.constraintColumns()).iterator(); 129 | constraintDef.append("("); 130 | while (columnIterator.hasNext()) { 131 | String columnName = columnIterator.next(); 132 | constraintDef.append(columnName); 133 | 134 | if (columnIterator.hasNext()) constraintDef.append(", "); 135 | } 136 | constraintDef.append(")"); 137 | 138 | return constraintDef; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/me/himanshusoni/quantumflux/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.sample; 2 | 3 | import android.accounts.Account; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.drawable.BitmapDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Bundle; 9 | import android.os.RemoteException; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.widget.ImageView; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import java.io.ByteArrayOutputStream; 18 | 19 | import me.himanshusoni.quantumflux.QuantumFlux; 20 | import me.himanshusoni.quantumflux.QuantumFluxSyncHelper; 21 | import me.himanshusoni.quantumflux.model.query.Select; 22 | import me.himanshusoni.quantumflux.model.util.QuantumFluxBatchDispatcher; 23 | import me.himanshusoni.quantumflux.sample.data.Author; 24 | import me.himanshusoni.quantumflux.sample.data.Book; 25 | import me.himanshusoni.quantumflux.sample.data.Publisher; 26 | import me.himanshusoni.quantumflux.sample.sync.SyncConstants; 27 | import me.himanshusoni.quantumflux.sample.sync.SyncUtils; 28 | 29 | public class MainActivity extends AppCompatActivity { 30 | 31 | // Instance fields 32 | Account mAccount; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_main); 38 | 39 | // For sync adapter 40 | mAccount = SyncUtils.createSyncAccount(this, "Dummy Account"); 41 | SyncUtils.startPeriodicSync(mAccount); 42 | 43 | // delete all previous data 44 | QuantumFlux.deleteAll(Book.class); 45 | QuantumFlux.deleteAll(Author.class); 46 | QuantumFlux.deleteAll(Publisher.class); 47 | 48 | Book book = new Book(); 49 | book.name = "Sorcerer's Stone"; 50 | book.isbn = "122342564"; 51 | 52 | Drawable drawable = getResources().getDrawable(R.drawable.hpsc); 53 | Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); 54 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 55 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); 56 | book.bookCover = stream.toByteArray(); 57 | 58 | QuantumFlux.insert(book); 59 | 60 | Author author = new Author(); 61 | author.name = "J.K. Rollings"; 62 | author.save(); 63 | 64 | Publisher publisher = new Publisher(); 65 | publisher.name = "Bloomsbury"; 66 | QuantumFlux.insert(publisher); 67 | 68 | 69 | ImageView cover = (ImageView) findViewById(R.id.cover); 70 | TextView bookName = (TextView) findViewById(R.id.book_name); 71 | TextView authorName = (TextView) findViewById(R.id.author_name); 72 | TextView pubName = (TextView) findViewById(R.id.publisher_name); 73 | 74 | Book firstBook = Select.from(Book.class).first(); 75 | Author firstAuthor = Select.from(Author.class).first(); 76 | Publisher firstPublisher = Select.from(Publisher.class).first(); 77 | 78 | bookName.setText(firstBook.name); 79 | authorName.setText(firstAuthor.name); 80 | pubName.setText(firstPublisher.name); 81 | 82 | 83 | Bitmap decodedByte = BitmapFactory.decodeByteArray(firstBook.bookCover, 0, firstBook.bookCover.length); 84 | cover.setImageBitmap(decodedByte); 85 | 86 | // update 87 | Author first = Select.from(Author.class).first(); 88 | first.name = "J. K. Rowling"; 89 | first.update(); 90 | 91 | authorName.setText(first.name); 92 | 93 | 94 | // update from QuantumFlux 95 | Book isbnBook = Select.from(Book.class).whereEquals("isbn", "122342564").first(); 96 | isbnBook.isbn = "122342567"; 97 | QuantumFlux.update(isbnBook); 98 | 99 | // batch insert example 100 | int batchSize = 100; 101 | QuantumFluxBatchDispatcher dispatcher = new QuantumFluxBatchDispatcher(this, Book.class, batchSize); 102 | for (int i = 0; i < batchSize; i++) { 103 | Book b = new Book(); 104 | b.name = "Book " + i; 105 | b.isbn = "ISBN" + b.hashCode(); 106 | dispatcher.add(b); 107 | } 108 | dispatcher.release(true); 109 | 110 | int count = Select.from(Book.class).queryAsCount(); 111 | Toast.makeText(this, "Total Books : " + count, Toast.LENGTH_LONG).show(); 112 | 113 | 114 | // To notify sync 115 | try { 116 | Book newBook = new Book(); 117 | newBook.name = "Chamber of Secrets"; 118 | QuantumFluxSyncHelper.insert(getContentResolver().acquireContentProviderClient(SyncConstants.AUTHORITY), newBook); 119 | } catch (RemoteException e) { 120 | e.printStackTrace(); 121 | } 122 | 123 | SyncUtils.refreshManually(mAccount); 124 | } 125 | 126 | @Override 127 | public boolean onCreateOptionsMenu(Menu menu) { 128 | // Inflate the menu; this adds items to the action bar if it is present. 129 | getMenuInflater().inflate(R.menu.menu_main, menu); 130 | return true; 131 | } 132 | 133 | @Override 134 | public boolean onOptionsItemSelected(MenuItem item) { 135 | // Handle action bar item clicks here. The action bar will 136 | // automatically handle clicks on the Home/Up button, so long 137 | // as you specify a parent activity in AndroidManifest.xml. 138 | int id = item.getItemId(); 139 | 140 | //noinspection SimplifiableIfStatement 141 | if (id == R.id.action_settings) { 142 | return true; 143 | } 144 | 145 | return super.onOptionsItemSelected(item); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/provider/util/UriMatcherHelper.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.provider.util; 2 | 3 | import android.content.ContentUris; 4 | import android.content.Context; 5 | import android.content.UriMatcher; 6 | import android.net.Uri; 7 | 8 | import java.util.LinkedHashMap; 9 | import java.util.Map; 10 | 11 | import me.himanshusoni.quantumflux.model.generate.ReflectionHelper; 12 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 13 | import me.himanshusoni.quantumflux.model.util.ManifestHelper; 14 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 15 | import me.himanshusoni.quantumflux.model.util.TableDetailsCache; 16 | 17 | /** 18 | * The uri matcher helper that will register all of the valid model url's that can be accessed. 19 | * Every item will have two URLs exposed, one to access all items, and another to access a single item. 20 | *
21 | * Each item is separated by a interval (Default 100) for the match code on the UriMatcher. With the 'all' and 'single' urls containing their own index 22 | * withing the 100 index gap between model items. 23 | */ 24 | public class UriMatcherHelper { 25 | 26 | public static int MATCHER_CODE_INTERVALS = 100; 27 | public static int MATCHER_ALL = 1; 28 | public static int MATCHER_SINGLE = 2; 29 | 30 | private final Map mMatcherCodes; 31 | private final String mAuthority; 32 | private UriMatcher mUriMatcher; 33 | 34 | public UriMatcherHelper(Context context) { 35 | this.mMatcherCodes = new LinkedHashMap(); 36 | mAuthority = ManifestHelper.getAuthority(context); 37 | } 38 | 39 | public void init(Context context, TableDetailsCache detailsCache) { 40 | mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 41 | int matcherInterval = MATCHER_CODE_INTERVALS; 42 | 43 | for (Class dataModelObject : ReflectionHelper.getDomainClasses(context, ReflectionHelper.TableType.TABLE)) { 44 | 45 | TableDetails tableDetails = detailsCache.findTableDetails(context, dataModelObject); 46 | 47 | mMatcherCodes.put(matcherInterval, tableDetails); 48 | mUriMatcher.addURI(mAuthority, tableDetails.getTableName(), matcherInterval + MATCHER_ALL); 49 | mUriMatcher.addURI(mAuthority, tableDetails.getTableName() + "/*", matcherInterval + MATCHER_SINGLE); 50 | 51 | matcherInterval += MATCHER_CODE_INTERVALS; 52 | } 53 | } 54 | 55 | public TableDetails getTableDetails(Uri uri) { 56 | 57 | int matchCode = mUriMatcher.match(uri); 58 | try { 59 | return findTableDetails(matchCode); 60 | } catch (Exception ex) { 61 | throw new QuantumFluxException("Could not find table information for Uri, make sure the model factory knows of this table: " + uri, ex); 62 | } 63 | } 64 | 65 | public String getType(Uri uri) { 66 | int matchCode = mUriMatcher.match(uri); 67 | TableDetails tableDetails = findTableDetails(matchCode); 68 | StringBuilder mimeType = new StringBuilder(); 69 | mimeType.append("android.cursor."); 70 | 71 | if (isSingleItemRequested(matchCode)) mimeType.append(".item"); 72 | else mimeType.append(".dir"); 73 | mimeType.append("/"); 74 | 75 | mimeType.append("vnd."); 76 | mimeType.append(mAuthority); 77 | if (mimeType.charAt(mimeType.length() - 1) != '.') mimeType.append("."); 78 | 79 | mimeType.append(tableDetails.getTableName()); 80 | 81 | return mimeType.toString(); 82 | } 83 | 84 | public boolean isSingleItemRequested(int code) { 85 | return mMatcherCodes.containsKey(code - MATCHER_SINGLE); 86 | } 87 | 88 | public boolean isSingleItemRequested(Uri uri) { 89 | return isSingleItemRequested(mUriMatcher.match(uri)); 90 | } 91 | 92 | public Uri generateItemUri(TableDetails tableDetails) { 93 | return new Uri.Builder() 94 | .scheme("content") 95 | .authority(tableDetails.getAuthority()) 96 | .appendEncodedPath(tableDetails.getTableName()) 97 | .build(); 98 | 99 | } 100 | 101 | public Uri generateSingleItemUri(TableDetails tableDetails, String itemId) { 102 | return new Uri.Builder() 103 | .scheme("content") 104 | .authority(tableDetails.getAuthority()) 105 | .appendEncodedPath(tableDetails.getTableName() + "/") 106 | .appendEncodedPath(itemId) 107 | .build(); 108 | 109 | } 110 | 111 | public Uri generateSingleItemUri(TableDetails tableDetails, long itemId) { 112 | return ContentUris.withAppendedId(generateItemUri(tableDetails), itemId); 113 | } 114 | 115 | private TableDetails findTableDetails(int code) { 116 | if (mMatcherCodes.containsKey(code)) return mMatcherCodes.get(code); 117 | else if (mMatcherCodes.containsKey(code - MATCHER_ALL)) 118 | return mMatcherCodes.get(code - MATCHER_ALL); 119 | else if (mMatcherCodes.containsKey(code - MATCHER_SINGLE)) 120 | return mMatcherCodes.get(code - MATCHER_SINGLE); 121 | else throw new QuantumFluxException("No URI match found for code: " + code); 122 | } 123 | 124 | public static Uri.Builder generateItemUriBuilder(TableDetails tableDetails) { 125 | String authority = tableDetails.getAuthority(); 126 | return new Uri.Builder() 127 | .scheme("content") 128 | .authority(authority) 129 | .appendEncodedPath(tableDetails.getTableName()); 130 | } 131 | 132 | public static Uri.Builder generateItemUriBuilder(TableDetails tableDetails, String itemId) { 133 | String authority = tableDetails.getAuthority(); 134 | 135 | return new Uri.Builder() 136 | .scheme("content") 137 | .authority(authority) 138 | .appendEncodedPath(tableDetails.getTableName()) 139 | .appendEncodedPath(itemId); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/QuantumFluxSyncHelper.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux; 2 | 3 | import android.content.ContentProviderClient; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.net.Uri; 7 | import android.os.RemoteException; 8 | 9 | import me.himanshusoni.quantumflux.model.generate.TableDetails; 10 | import me.himanshusoni.quantumflux.model.util.ModelInflater; 11 | import me.himanshusoni.quantumflux.provider.QuantumFluxContentProvider; 12 | import me.himanshusoni.quantumflux.provider.util.UriMatcherHelper; 13 | 14 | /** 15 | * QuantumFlux sync helper for sync adapter 16 | */ 17 | public class QuantumFluxSyncHelper { 18 | @SafeVarargs 19 | public static void insert(ContentProviderClient provider, T... dataModelObjects) throws RemoteException { 20 | 21 | if (dataModelObjects.length == 1) { 22 | T modelObject = dataModelObjects[0]; 23 | TableDetails tableDetails = QuantumFlux.findTableDetails(modelObject.getClass()); 24 | ContentValues contentValues = ModelInflater.deflate(tableDetails, modelObject); 25 | Uri insertUri = UriMatcherHelper.generateItemUriBuilder(tableDetails) 26 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 27 | 28 | provider.insert(insertUri, contentValues); 29 | } else { 30 | TableDetails tableDetails = QuantumFlux.findTableDetails(dataModelObjects[0].getClass()); 31 | ContentValues[] insertObjects = ModelInflater.deflateAll(tableDetails, dataModelObjects); 32 | 33 | if (tableDetails != null) { 34 | Uri insertUri = UriMatcherHelper.generateItemUriBuilder(tableDetails) 35 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 36 | provider.bulkInsert(insertUri, insertObjects); 37 | } 38 | } 39 | } 40 | 41 | public static T insertAndReturn(ContentProviderClient provider, T dataModelObject) throws RemoteException { 42 | TableDetails tableDetails = QuantumFlux.findTableDetails(dataModelObject.getClass()); 43 | ContentValues contentValues = ModelInflater.deflate(tableDetails, dataModelObject); 44 | Uri insertUri = UriMatcherHelper.generateItemUriBuilder(tableDetails) 45 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 46 | 47 | Uri itemUri = provider.insert(insertUri, contentValues); 48 | 49 | return QuantumFlux.findSingleItem(itemUri, tableDetails); 50 | } 51 | 52 | public static void update(Context context, ContentProviderClient provider, T dataModelObject) throws RemoteException { 53 | TableDetails tableDetails = QuantumFlux.findTableDetails(dataModelObject.getClass()); 54 | ContentValues contentValues = ModelInflater.deflate(tableDetails, dataModelObject); 55 | Object columnValue = ModelInflater.deflateColumn(tableDetails, tableDetails.findPrimaryKeyColumn(), dataModelObject); 56 | Uri itemUri = UriMatcherHelper.generateItemUriBuilder(tableDetails, String.valueOf(columnValue)) 57 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 58 | 59 | provider.update(itemUri, contentValues, null, null); 60 | } 61 | 62 | public static void updateColumns(Context context, ContentProviderClient provider, T dataModelObject, String... columns) throws RemoteException { 63 | TableDetails tableDetails = QuantumFlux.findTableDetails(dataModelObject.getClass()); 64 | ContentValues contentValues = ModelInflater.deflate(tableDetails, dataModelObject); 65 | Object columnValue = ModelInflater.deflateColumn(tableDetails, tableDetails.findPrimaryKeyColumn(), dataModelObject); 66 | Uri itemUri = UriMatcherHelper.generateItemUriBuilder(tableDetails, String.valueOf(columnValue)) 67 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 68 | 69 | for (String contentColumn : tableDetails.getColumnNames()) { 70 | boolean includeColumn = false; 71 | for (String column : columns) { 72 | if (contentColumn.equals(column)) { 73 | includeColumn = true; 74 | break; 75 | } 76 | } 77 | 78 | if (!includeColumn) contentValues.remove(contentColumn); 79 | } 80 | 81 | provider.update(itemUri, contentValues, null, null); 82 | } 83 | 84 | public static void updateColumnsExcluding(Context context, ContentProviderClient provider, T dataModelObject, String... columnsToExclude) throws RemoteException { 85 | TableDetails tableDetails = QuantumFlux.findTableDetails(dataModelObject.getClass()); 86 | ContentValues contentValues = ModelInflater.deflate(tableDetails, dataModelObject); 87 | Object columnValue = ModelInflater.deflateColumn(tableDetails, tableDetails.findPrimaryKeyColumn(), dataModelObject); 88 | Uri itemUri = UriMatcherHelper.generateItemUriBuilder(tableDetails, String.valueOf(columnValue)) 89 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 90 | 91 | for (String columnToExclude : columnsToExclude) { 92 | contentValues.remove(columnToExclude); 93 | } 94 | 95 | provider.update(itemUri, contentValues, null, null); 96 | } 97 | 98 | public static void delete(Context context, ContentProviderClient provider, T dataModelObject) throws RemoteException { 99 | TableDetails tableDetails = QuantumFlux.findTableDetails(dataModelObject.getClass()); 100 | Object columnValue = ModelInflater.deflateColumn(tableDetails, tableDetails.findPrimaryKeyColumn(), dataModelObject); 101 | Uri itemUri = UriMatcherHelper.generateItemUriBuilder(tableDetails, String.valueOf(columnValue)) 102 | .appendQueryParameter(QuantumFluxContentProvider.PARAMETER_SYNC, "false").build(); 103 | 104 | provider.delete(itemUri, null, null); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/query/DataFilterCriteria.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.query; 2 | 3 | import java.util.Iterator; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | 7 | import me.himanshusoni.quantumflux.model.map.SqlColumnMappingFactory; 8 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 9 | 10 | /** 11 | * Filter mCriteria usually contain one or more filter clauses, it is a grouping of 12 | * queries, each separated by a Conjunction. This entire Criteria is a grouping of its own, and 13 | * as such will be wrapped in parenthesis. 14 | */ 15 | public class DataFilterCriteria implements DataFilterClause { 16 | 17 | private final Map mFilterClauses; 18 | 19 | /** 20 | * Creates a new instance, using the context to determine the conversion for arguments to sql friendly format 21 | */ 22 | public DataFilterCriteria() { 23 | mFilterClauses = new LinkedHashMap(); 24 | } 25 | 26 | public DataFilterCriteria addClause(DataFilterClause clause) { 27 | addClause(clause, null); 28 | 29 | return this; 30 | } 31 | 32 | @Override 33 | public DataFilterCriteria addClause(DataFilterClause clause, DataFilterConjunction conjunction) { 34 | if (conjunction == null) conjunction = DataFilterConjunction.AND; 35 | 36 | if (clause != null) 37 | mFilterClauses.put(clause, conjunction); 38 | 39 | return this; 40 | } 41 | 42 | public DataFilterCriteria addCriterion(String column, DataFilterCriterion.DataFilterOperator operator, Object filterValue) { 43 | addClause(new DataFilterCriterion(column, operator, filterValue)); 44 | 45 | return this; 46 | } 47 | 48 | @Override 49 | public QueryBuilder buildWhereClause(SqlColumnMappingFactory columnMappingFactory) { 50 | QueryBuilder builder = new QueryBuilder(); 51 | 52 | if (!mFilterClauses.isEmpty()) { 53 | boolean isFirst = true; 54 | Iterator clauseIterator = mFilterClauses.keySet().iterator(); 55 | builder.append("("); 56 | while (clauseIterator.hasNext()) { 57 | DataFilterClause clause = clauseIterator.next(); 58 | if (!isFirst) { 59 | builder.append(mFilterClauses.get(clause).toString()); 60 | builder.append(" "); 61 | } else isFirst = false; 62 | 63 | builder.append(clause.buildWhereClause(columnMappingFactory)); 64 | 65 | if (clauseIterator.hasNext()) builder.append(" "); 66 | } 67 | 68 | builder.append(")"); 69 | } 70 | 71 | return builder; 72 | } 73 | 74 | @Override 75 | public String getWhereClause() { 76 | QueryBuilder builder = new QueryBuilder(); 77 | 78 | if (hasFilterValue()) { 79 | DataFilterClause previousClause = null; 80 | Iterator clauseIterator = mFilterClauses.keySet().iterator(); 81 | builder.append("("); 82 | while (clauseIterator.hasNext()) { 83 | 84 | DataFilterClause clause = clauseIterator.next(); 85 | if (!clause.hasFilterValue()) 86 | continue; 87 | 88 | if (previousClause != null && previousClause.hasFilterValue()) { 89 | builder.append(mFilterClauses.get(previousClause).toString()); 90 | builder.append(" "); 91 | } 92 | 93 | builder.append(clause.getWhereClause()); 94 | previousClause = clause; 95 | 96 | if (clauseIterator.hasNext()) builder.append(" "); 97 | } 98 | 99 | builder.append(")"); 100 | } 101 | 102 | return builder.toString(); 103 | } 104 | 105 | @Override 106 | public boolean hasFilterValue() { 107 | boolean hasFilterValue = false; 108 | 109 | for (DataFilterClause clause : mFilterClauses.keySet()) { 110 | hasFilterValue = hasFilterValue || clause.hasFilterValue(); 111 | } 112 | return hasFilterValue; 113 | } 114 | 115 | public static class Builder> implements DataFilterClause> { 116 | 117 | private final T mOriginator; 118 | private final DataFilterConjunction mConjunction; 119 | private final DataFilterCriteria mCriteria; 120 | 121 | protected Builder(T originator, DataFilterConjunction conjunction) { 122 | this.mOriginator = originator; 123 | this.mConjunction = conjunction; 124 | this.mCriteria = new DataFilterCriteria(); 125 | } 126 | 127 | public DataFilterCriterion.Builder> and() { 128 | return new DataFilterCriterion.Builder>(this, DataFilterClause.DataFilterConjunction.AND); 129 | } 130 | 131 | public Builder and(DataFilterClause dataFilterClause) { 132 | 133 | return addClause(dataFilterClause, DataFilterConjunction.AND); 134 | } 135 | 136 | public DataFilterCriterion.Builder> or() { 137 | return new DataFilterCriterion.Builder>(this, DataFilterClause.DataFilterConjunction.OR); 138 | } 139 | 140 | public Builder or(DataFilterClause dataFilterClause) { 141 | return addClause(dataFilterClause, DataFilterConjunction.OR); 142 | } 143 | 144 | public Builder> openBracketAnd() { 145 | return new Builder>(this, DataFilterConjunction.AND); 146 | } 147 | 148 | public Builder> openBracketOr() { 149 | return new Builder>(this, DataFilterConjunction.OR); 150 | } 151 | 152 | public T closeBracket() { 153 | mOriginator.addClause(mCriteria, mConjunction); 154 | return mOriginator; 155 | } 156 | 157 | @Override 158 | public QueryBuilder buildWhereClause(SqlColumnMappingFactory columnMappingFactory) { 159 | throw new QuantumFluxException("This cannot be called on a builder"); 160 | } 161 | 162 | @Override 163 | public String getWhereClause() { 164 | throw new QuantumFluxException("This cannot be called on a builder"); 165 | } 166 | 167 | @Override 168 | public Builder addClause(DataFilterClause clause, DataFilterConjunction conjunction) { 169 | mCriteria.addClause(clause, conjunction); 170 | return this; 171 | } 172 | 173 | @Override 174 | public boolean hasFilterValue() { 175 | return mCriteria.hasFilterValue(); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/generate/TableDetails.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.generate; 2 | 3 | import android.content.ContentValues; 4 | import android.database.Cursor; 5 | import android.text.TextUtils; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | import me.himanshusoni.quantumflux.model.annotation.Index; 16 | import me.himanshusoni.quantumflux.model.annotation.TableConstraint; 17 | import me.himanshusoni.quantumflux.model.map.SqlColumnMapping; 18 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 19 | 20 | /** 21 | * This class will contain all of the information retrieved from the reflection of a java object. 22 | * The goal is to do the reflection once, and then use this object from there on, onwards. This should help us a bit with performance 23 | * and it contains use full quick shortcuts for manipulating java objects to and from sql. 24 | */ 25 | public class TableDetails { 26 | 27 | private final String mTableName; 28 | private final String mAuthority; 29 | private final Class mTableClass; 30 | private final Constructor mTableClassConstructor; 31 | private final List mColumns = new LinkedList<>(); 32 | private final List mIndices = new LinkedList<>(); 33 | private final List mConstraints = new LinkedList<>(); 34 | private final List> mChangeListener = new LinkedList<>(); 35 | 36 | public TableDetails(String tableName, String authority, Class tableClass) { 37 | this.mTableName = tableName; 38 | this.mAuthority = authority; 39 | this.mTableClass = tableClass; 40 | try { 41 | mTableClassConstructor = tableClass.getConstructor(); 42 | mTableClassConstructor.setAccessible(true); 43 | } catch (Exception ex) { 44 | throw new QuantumFluxException("Could not create a new instance of data model object: " + tableName); 45 | } 46 | } 47 | 48 | public String getTableName() { 49 | return mTableName; 50 | } 51 | 52 | public String getAuthority() { 53 | return mAuthority; 54 | } 55 | 56 | public Class getTableClass() { 57 | return mTableClass; 58 | } 59 | 60 | public Object createNewModelInstance() throws IllegalAccessException, InvocationTargetException, InstantiationException { 61 | return mTableClassConstructor.newInstance(); 62 | } 63 | 64 | public ColumnDetails findPrimaryKeyColumn() { 65 | for (int i = 0; i < mColumns.size(); i++) { 66 | ColumnDetails column = mColumns.get(i); 67 | if (column.isPrimaryKey()) return column; 68 | } 69 | return null; 70 | } 71 | 72 | public String[] getColumnNames() { 73 | String[] columnNames = new String[mColumns.size()]; 74 | for (int i = 0; i < mColumns.size(); i++) { 75 | ColumnDetails columnDetails = mColumns.get(i); 76 | columnNames[i] = columnDetails.getColumnName(); 77 | } 78 | return columnNames; 79 | } 80 | 81 | public List getColumns() { 82 | return Collections.unmodifiableList(mColumns); 83 | } 84 | 85 | public ColumnDetails findColumn(String name) { 86 | for (int i = 0; i < mColumns.size(); i++) { 87 | ColumnDetails column = mColumns.get(i); 88 | if (column.getColumnName().equalsIgnoreCase(name)) 89 | return column; 90 | } 91 | return null; 92 | } 93 | 94 | public void addColumn(ColumnDetails column) { 95 | mColumns.add(column); 96 | 97 | boolean hasPrimaryKey = false; 98 | for (ColumnDetails columnDetails : mColumns) { 99 | if (hasPrimaryKey && columnDetails.isPrimaryKey()) 100 | throw new QuantumFluxException("Table may only have one primary key constraint on column definition, is a table mConstraints to specify more than one"); 101 | hasPrimaryKey = hasPrimaryKey || columnDetails.isPrimaryKey(); 102 | } 103 | } 104 | 105 | public List getIndices() { 106 | return Collections.unmodifiableList(mIndices); 107 | } 108 | 109 | public void addIndex(Index index) { 110 | mIndices.add(index); 111 | } 112 | 113 | public List> getChangeListeners() { 114 | return Collections.unmodifiableList(mChangeListener); 115 | } 116 | 117 | public void addChangeListener(Class clazz) { 118 | mChangeListener.add(clazz); 119 | } 120 | 121 | public Collection getConstraints() { 122 | return mConstraints; 123 | } 124 | 125 | public void addConstraint(TableConstraint contConstraint) { 126 | mConstraints.add(contConstraint); 127 | } 128 | 129 | /** 130 | * Contains all of the column information supplied by the {@link me.himanshusoni.quantumflux.model.annotation.Column} and other annotations 131 | * on the java fields. It also contains a column mapping that will be used to convert java objects to and from SQL. 132 | */ 133 | public static class ColumnDetails { 134 | private final String mColumnName; 135 | private final Field mColumnField; 136 | private final SqlColumnMapping mColumnMapping; 137 | // private final Class mReference; 138 | private final boolean isPrimaryKey; 139 | private final boolean isUnique; 140 | private final boolean isRequired; 141 | private final boolean isAutoIncrement; 142 | private final boolean mNotifyChanges; 143 | 144 | public ColumnDetails(String columnName, Field columnField, SqlColumnMapping columnTypeMapping, 145 | // Class references, 146 | boolean primaryKey, boolean unique, boolean required, boolean autoIncrement, 147 | boolean notifyChanges) { 148 | this.mColumnName = columnName; 149 | this.mColumnField = columnField; 150 | this.mColumnMapping = columnTypeMapping; 151 | // this.mReference = references; 152 | this.isPrimaryKey = primaryKey || autoIncrement; 153 | this.isUnique = unique; 154 | this.isRequired = required; 155 | this.isAutoIncrement = autoIncrement; 156 | this.mNotifyChanges = notifyChanges; 157 | 158 | if (primaryKey && !required) { 159 | throw new QuantumFluxException("Column must be not isRequired if primary key is set"); 160 | } 161 | 162 | if (TextUtils.isEmpty(columnName)) { 163 | throw new QuantumFluxException("A valid column name needs to be provided"); 164 | } 165 | 166 | columnField.setAccessible(true); 167 | } 168 | 169 | public String getColumnName() { 170 | return mColumnName; 171 | } 172 | 173 | public SqlColumnMapping getColumnTypeMapping() { 174 | return mColumnMapping; 175 | } 176 | 177 | public Field getColumnField() { 178 | return mColumnField; 179 | } 180 | 181 | public boolean isPrimaryKey() { 182 | return isPrimaryKey; 183 | } 184 | 185 | public boolean isUnique() { 186 | return isUnique; 187 | } 188 | 189 | public boolean isRequired() { 190 | return isRequired; 191 | } 192 | 193 | public boolean isAutoIncrement() { 194 | return isAutoIncrement; 195 | } 196 | 197 | public boolean notifyChanges() { 198 | return mNotifyChanges; 199 | } 200 | 201 | 202 | public void setFieldValue(Cursor cursor, int columnIndex, Object dataModelObject) throws IllegalAccessException { 203 | mColumnField.set(dataModelObject, mColumnMapping.getColumnValue(cursor, columnIndex)); 204 | } 205 | 206 | public void setContentValue(ContentValues contentValues, Object dataModelObject) throws IllegalAccessException { 207 | 208 | Object value = mColumnField.get(dataModelObject); 209 | 210 | if (value == null) contentValues.putNull(mColumnName); 211 | else mColumnMapping.setColumnValue(contentValues, mColumnName, value); 212 | } 213 | 214 | // public Class getReference() { 215 | // return mReference; 216 | // } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /quantum-flux/quantum-flux.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /quantum-flux/src/main/java/me/himanshusoni/quantumflux/model/query/DataFilterCriterion.java: -------------------------------------------------------------------------------- 1 | package me.himanshusoni.quantumflux.model.query; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.util.Collection; 6 | import java.util.Iterator; 7 | 8 | import me.himanshusoni.quantumflux.model.map.SqlColumnMappingFactory; 9 | import me.himanshusoni.quantumflux.model.util.QuantumFluxException; 10 | 11 | /** 12 | * A filter mCriterion is single SQL condition. The filter column, operator, and filter value must be supplied 13 | * for this mCriterion to be valid. Filter values are automatically converted to the correct sql format, and 14 | * the sql % are automatically added in the correct locations based on the operator used. 15 | */ 16 | public class DataFilterCriterion implements DataFilterClause { 17 | 18 | public String mFilterColumn; 19 | public DataFilterOperator mFilterOperator; 20 | public Object mFilterValue; 21 | 22 | /** 23 | * Creates a new instance, using the context to determine the conversion for arguments to sql friendly format 24 | */ 25 | private DataFilterCriterion() { 26 | } 27 | 28 | /** 29 | * Creates a new instance, using the context to determine the conversion for arguments to sql friendly format. 30 | * The additional arguments can be used to set the values for the mCriterion. 31 | * 32 | * @param filterColumn The column to query compare. 33 | * @param operator The operator used for comparison. 34 | */ 35 | public DataFilterCriterion(String filterColumn, DataFilterOperator operator, Object filterValue) { 36 | this.mFilterColumn = filterColumn; 37 | this.mFilterOperator = operator; 38 | this.mFilterValue = filterValue; 39 | } 40 | 41 | @Override 42 | public QueryBuilder buildWhereClause(SqlColumnMappingFactory columnMappingFactory) { 43 | QueryBuilder builder = new QueryBuilder(); 44 | builder.append(mFilterColumn); 45 | builder.append(" "); 46 | builder.append(mFilterOperator.getSqlRepresentation()); 47 | 48 | if (mFilterValue != null) { 49 | if (mFilterValue instanceof Collection) { 50 | Iterator collectionIterator = ((Collection) mFilterValue).iterator(); 51 | builder.append(" ("); 52 | 53 | while (collectionIterator.hasNext()) { 54 | builder.append("?", convertToSQLFormat(columnMappingFactory, collectionIterator.next())); 55 | if (collectionIterator.hasNext()) builder.append(", "); 56 | } 57 | builder.append(")"); 58 | } else if (mFilterValue instanceof Select) { 59 | Select innerSelect = (Select) mFilterValue; 60 | if (!innerSelect.isSingleColumnProjection()) 61 | throw new QuantumFluxException("Inner select can only contain a single column selection"); 62 | 63 | builder.append(" ("); 64 | builder.append((innerSelect).getSelectQuery()); 65 | builder.append(")"); 66 | } else builder.append(" ?", convertToSQLFormat(columnMappingFactory, mFilterValue)); 67 | } 68 | 69 | return builder; 70 | } 71 | 72 | @Override 73 | public String getWhereClause() { 74 | QueryBuilder builder = new QueryBuilder(); 75 | builder.append(mFilterColumn); 76 | builder.append(" "); 77 | builder.append(mFilterOperator.getSqlRepresentation()); 78 | 79 | if (mFilterValue != null) { 80 | if (mFilterValue instanceof Collection) { 81 | Iterator collectionIterator = ((Collection) mFilterValue).iterator(); 82 | builder.append(" ("); 83 | 84 | while (collectionIterator.hasNext()) { 85 | builder.append("?"); 86 | if (collectionIterator.hasNext()) builder.append(", "); 87 | } 88 | builder.append(")"); 89 | 90 | } else if (mFilterValue instanceof Select) { 91 | Select innerSelect = (Select) mFilterValue; 92 | if (!innerSelect.isSingleColumnProjection()) 93 | throw new QuantumFluxException("Inner select can only contain a single column selection"); 94 | 95 | builder.append(" ("); 96 | builder.append((innerSelect).getSelectQuery()); 97 | builder.append(")"); 98 | } else builder.append(" ?"); 99 | } 100 | 101 | return builder.toString(); 102 | } 103 | 104 | @Override 105 | public boolean hasFilterValue() { 106 | return mFilterColumn != null && mFilterOperator != null; 107 | } 108 | 109 | @Override 110 | public DataFilterCriterion addClause(DataFilterClause clause, DataFilterConjunction conjunction) { 111 | throw new QuantumFluxException("Clauses cannot be added to a data filter mCriterion"); 112 | } 113 | 114 | private Object convertToSQLFormat(SqlColumnMappingFactory columnMappingFactory, Object object) { 115 | if (mFilterOperator == DataFilterOperator.LIKE || mFilterOperator == DataFilterOperator.NOT_LIKE) 116 | return "%" + object + "%"; 117 | else if (mFilterOperator == DataFilterOperator.BEGINS_WITH) return object + "%"; 118 | else if (mFilterOperator == DataFilterOperator.ENDS_WITH) return "%" + object; 119 | else return columnMappingFactory.findColumnMapping(object.getClass()).toSqlType(object); 120 | } 121 | 122 | private void validate() { 123 | if (TextUtils.isEmpty(mFilterColumn)) 124 | throw new QuantumFluxException("Filter column is empty"); 125 | if (mFilterOperator == null) 126 | throw new QuantumFluxException("Filter operator not specified"); 127 | if (mFilterValue == null && mFilterOperator != DataFilterOperator.IS_NULL && mFilterOperator != DataFilterOperator.IS_NOT_NULL) 128 | throw new QuantumFluxException("Filter value must be supplied with this operator"); 129 | } 130 | 131 | private void setFilterColumn(String filterColumn) { 132 | this.mFilterColumn = filterColumn; 133 | } 134 | 135 | private void setFilterOperator(DataFilterOperator filterOperator) { 136 | this.mFilterOperator = filterOperator; 137 | } 138 | 139 | private void setFilterValue(Object filterValue) { 140 | this.mFilterValue = filterValue; 141 | } 142 | 143 | public static class Builder> { 144 | 145 | private final T mOriginator; 146 | private final DataFilterConjunction mConjunction; 147 | private final DataFilterCriterion mCriterion; 148 | 149 | protected Builder(T originator, DataFilterConjunction conjunction) { 150 | this.mOriginator = originator; 151 | this.mConjunction = conjunction; 152 | mCriterion = new DataFilterCriterion(); 153 | } 154 | 155 | public T equal(String column, Object value) { 156 | column(column); 157 | mCriterion.setFilterOperator(DataFilterOperator.EQUAL); 158 | return value(value); 159 | } 160 | 161 | public T notEqual(String column, Object value) { 162 | column(column); 163 | mCriterion.setFilterOperator(DataFilterOperator.NOT_EQUAL); 164 | return value(value); 165 | } 166 | 167 | public T greaterOrEqual(String column, Object value) { 168 | column(column); 169 | mCriterion.setFilterOperator(DataFilterOperator.GREATER_OR_EQUAL); 170 | return value(value); 171 | } 172 | 173 | public T smallerOrEqual(String column, Object value) { 174 | column(column); 175 | mCriterion.setFilterOperator(DataFilterOperator.SMALLER_OR_EQUAL); 176 | return value(value); 177 | } 178 | 179 | public T greaterThan(String column, Object value) { 180 | column(column); 181 | mCriterion.setFilterOperator(DataFilterOperator.GREATER_THAN); 182 | return value(value); 183 | } 184 | 185 | public T smallerThan(String column, Object value) { 186 | column(column); 187 | mCriterion.setFilterOperator(DataFilterOperator.SMALLER_THAN); 188 | return value(value); 189 | } 190 | 191 | public T like(String column, Object value) { 192 | column(column); 193 | mCriterion.setFilterOperator(DataFilterOperator.LIKE); 194 | return value(value); 195 | } 196 | 197 | public T notLike(String column, Object value) { 198 | column(column); 199 | mCriterion.setFilterOperator(DataFilterOperator.NOT_LIKE); 200 | return value(value); 201 | } 202 | 203 | public T in(String column, Collection value) { 204 | column(column); 205 | mCriterion.setFilterOperator(DataFilterOperator.IN); 206 | return value(value); 207 | } 208 | 209 | public T in(String column, Select value) { 210 | column(column); 211 | mCriterion.setFilterOperator(DataFilterOperator.IN); 212 | return value(value); 213 | } 214 | 215 | public T notIn(String column, Collection value) { 216 | column(column); 217 | mCriterion.setFilterOperator(DataFilterOperator.NOT_IN); 218 | return value(value); 219 | } 220 | 221 | public T notIn(String column, Select value) { 222 | column(column); 223 | mCriterion.setFilterOperator(DataFilterOperator.NOT_IN); 224 | return value(value); 225 | } 226 | 227 | public T isNull(String column) { 228 | column(column); 229 | mCriterion.setFilterOperator(DataFilterOperator.IS_NULL); 230 | return build(); 231 | } 232 | 233 | public T isNotNull(String column) { 234 | column(column); 235 | mCriterion.setFilterOperator(DataFilterOperator.IS_NOT_NULL); 236 | return build(); 237 | } 238 | 239 | private Builder column(String column) { 240 | mCriterion.setFilterColumn(column); 241 | return this; 242 | } 243 | 244 | private T value(Object value) { 245 | mCriterion.setFilterValue(value); 246 | return build(); 247 | } 248 | 249 | private T build() { 250 | mCriterion.validate(); 251 | mOriginator.addClause(mCriterion, mConjunction); 252 | return mOriginator; 253 | } 254 | } 255 | 256 | public enum DataFilterOperator { 257 | EQUAL("="), 258 | NOT_EQUAL("<>"), 259 | GREATER_OR_EQUAL(">="), 260 | SMALLER_OR_EQUAL("<="), 261 | GREATER_THAN(">"), 262 | SMALLER_THAN("<"), 263 | LIKE("LIKE"), 264 | NOT_LIKE("NOT LIKE"), 265 | IN("IN"), 266 | NOT_IN("NOT IN"), 267 | IS_NULL("IS NULL"), 268 | IS_NOT_NULL("IS NOT NULL"), 269 | BEGINS_WITH("LIKE"), 270 | ENDS_WITH("LIKE"); 271 | 272 | private final String sqlRepresentation; 273 | 274 | DataFilterOperator(String sqlRepresentation) { 275 | this.sqlRepresentation = sqlRepresentation; 276 | } 277 | 278 | public String getSqlRepresentation() { 279 | return sqlRepresentation; 280 | } 281 | } 282 | } 283 | --------------------------------------------------------------------------------