diff --git a/.classpath b/.classpath index 24761b6..f66896c 100644 --- a/.classpath +++ b/.classpath @@ -4,5 +4,5 @@ - + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0008167 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# osx noise +.DS_Store +profile + +# xcode noise +build/* +*.mode1 +*.mode1v3 +*.mode2v3 +*.perspective +*.perspectivev3 +*.pbxuser +*.xcworkspace +xcuserdata + +# svn & cvs +.svn +CVS diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0a7d842..868f338 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,11 +1,11 @@ - + - @@ -25,8 +25,8 @@ + android:targetPackage="com.perfectworldprogramming.mobile.orm.test" + android:name="android.test.InstrumentationTestRunner" /--> diff --git a/README b/README index e69de29..283c987 100644 --- a/README +++ b/README @@ -0,0 +1,15 @@ +Welcome to AndroidSQLLiteOrm. + +The purpose of this project is to simplify the Android SQLLite apis. I found the apis to be un-intuitive. Now un-intuitive to one person is different to another. So to many out there the Android SQLLite apis are perfectly fine. Not to me, so I thought about projects that are already out there that simplify Data Access. And since I do a lot of work with the Spring Framework, I thought I could match Spring's JdbcTemplate api and make it run on Android. And that is exactly what I have done. + +So, if you have used Spring's JdbcTemplate before, then you know how to use AndroidSQLLiteOrm. + +I have added a couple of features above the JdbcTemplate. I have added basic ORM support. Very generic and basic. So I do not do associations. I do Foreign Key Associations of ManyToOne, simple for saving Child records to have the FK stored in its table. But I do not retrieve any associations when querying. The ORM support works really well with Creating and Android database, where Create Table scripts can be automatically generated, as well as Alter Table scripts for updates to your database version. + +For the best documentation on how to use this project. Check out the full set of Unit and Integration tests. You can even run the tests if you like to prove that it works. + +Thank you for looking at my project + +Mark Spritzler + +Please raise issues/Feedback and comments on GitHub to this project. \ No newline at end of file diff --git a/bin/classes.dex b/bin/classes.dex new file mode 100644 index 0000000..369885c Binary files /dev/null and b/bin/classes.dex differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/AndroidSQLiteTemplate.class b/bin/classes/com/perfectworldprogramming/mobile/orm/AndroidSQLiteTemplate.class new file mode 100644 index 0000000..fdb93f4 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/AndroidSQLiteTemplate.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/CursorAdapter.class b/bin/classes/com/perfectworldprogramming/mobile/orm/CursorAdapter.class new file mode 100644 index 0000000..f299679 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/CursorAdapter.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/Main.class b/bin/classes/com/perfectworldprogramming/mobile/orm/Main.class new file mode 100644 index 0000000..245b4b5 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/Main.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/R$attr.class b/bin/classes/com/perfectworldprogramming/mobile/orm/R$attr.class new file mode 100644 index 0000000..3a7b47c Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/R$attr.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/R$drawable.class b/bin/classes/com/perfectworldprogramming/mobile/orm/R$drawable.class new file mode 100644 index 0000000..580e5fd Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/R$drawable.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/R$layout.class b/bin/classes/com/perfectworldprogramming/mobile/orm/R$layout.class new file mode 100644 index 0000000..48b1c57 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/R$layout.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/R$string.class b/bin/classes/com/perfectworldprogramming/mobile/orm/R$string.class new file mode 100644 index 0000000..e1dc558 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/R$string.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/R.class b/bin/classes/com/perfectworldprogramming/mobile/orm/R.class new file mode 100644 index 0000000..b5a952b Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/R.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/Column.class b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/Column.class new file mode 100644 index 0000000..8474728 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/Column.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ColumnSetter.class b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ColumnSetter.class new file mode 100644 index 0000000..9acf531 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ColumnSetter.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ColumnType.class b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ColumnType.class new file mode 100644 index 0000000..bee0f0c Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ColumnType.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ForeignKey.class b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ForeignKey.class new file mode 100644 index 0000000..4eaa74f Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/ForeignKey.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/PrimaryKey.class b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/PrimaryKey.class new file mode 100644 index 0000000..02a7774 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/PrimaryKey.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/Transient.class b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/Transient.class new file mode 100644 index 0000000..7aa8a2f Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/annotations/Transient.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/creator/CreateStatementGenerator.class b/bin/classes/com/perfectworldprogramming/mobile/orm/creator/CreateStatementGenerator.class new file mode 100644 index 0000000..e0efa64 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/creator/CreateStatementGenerator.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/creator/SQLLiteCreateStatementGenerator.class b/bin/classes/com/perfectworldprogramming/mobile/orm/creator/SQLLiteCreateStatementGenerator.class new file mode 100644 index 0000000..cd0b797 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/creator/SQLLiteCreateStatementGenerator.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/DataAccessException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/DataAccessException.class new file mode 100644 index 0000000..51e23d3 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/DataAccessException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/EmptySQLStatementException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/EmptySQLStatementException.class new file mode 100644 index 0000000..e0b2e23 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/EmptySQLStatementException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/ExtraResultsException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/ExtraResultsException.class new file mode 100644 index 0000000..23b98bf Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/ExtraResultsException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorExtractorException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorExtractorException.class new file mode 100644 index 0000000..1447658 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorExtractorException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorRowMapperException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorRowMapperException.class new file mode 100644 index 0000000..c39a8a1 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorRowMapperException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidQueryTypeException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidQueryTypeException.class new file mode 100644 index 0000000..7ce270a Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/InvalidQueryTypeException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/NoPrimaryKeyFieldException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/NoPrimaryKeyFieldException.class new file mode 100644 index 0000000..0ceb533 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/NoPrimaryKeyFieldException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/exception/NoRowsReturnedException.class b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/NoRowsReturnedException.class new file mode 100644 index 0000000..0329342 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/exception/NoRowsReturnedException.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/helper/AndroidSQLLiteOpenHelper.class b/bin/classes/com/perfectworldprogramming/mobile/orm/helper/AndroidSQLLiteOpenHelper.class new file mode 100644 index 0000000..056708b Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/helper/AndroidSQLLiteOpenHelper.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/helper/DBHelper.class b/bin/classes/com/perfectworldprogramming/mobile/orm/helper/DBHelper.class new file mode 100644 index 0000000..ba7703e Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/helper/DBHelper.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/CursorExtractor.class b/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/CursorExtractor.class new file mode 100644 index 0000000..6f59a0f Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/CursorExtractor.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/CursorRowMapper.class b/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/CursorRowMapper.class new file mode 100644 index 0000000..b48415f Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/CursorRowMapper.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/JdbcOperations.class b/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/JdbcOperations.class new file mode 100644 index 0000000..bc23a22 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/interfaces/JdbcOperations.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/reflection/DomainClassAnalyzer.class b/bin/classes/com/perfectworldprogramming/mobile/orm/reflection/DomainClassAnalyzer.class new file mode 100644 index 0000000..ce4c49c Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/reflection/DomainClassAnalyzer.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/AndroidSQLiteTemplateTests.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/AndroidSQLiteTemplateTests.class new file mode 100644 index 0000000..7ac727a Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/AndroidSQLiteTemplateTests.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/Main.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/Main.class new file mode 100644 index 0000000..8bc5b34 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/Main.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/MyInstrumentationTestRunner.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/MyInstrumentationTestRunner.class new file mode 100644 index 0000000..ecf0a66 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/MyInstrumentationTestRunner.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/creator/TableCreatorTest.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/creator/TableCreatorTest.class new file mode 100644 index 0000000..d0a7454 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/creator/TableCreatorTest.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Account.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Account.class new file mode 100644 index 0000000..a014621 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Account.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Address.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Address.class new file mode 100644 index 0000000..48cc6f8 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Address.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/NoPKDomain.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/NoPKDomain.class new file mode 100644 index 0000000..2ec7601 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/NoPKDomain.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Person.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Person.class new file mode 100644 index 0000000..2358e41 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/Person.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/WrongTypeMappedDomain.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/WrongTypeMappedDomain.class new file mode 100644 index 0000000..fc4823f Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/domain/WrongTypeMappedDomain.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.class new file mode 100644 index 0000000..4d1b015 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/helper/DBHelperTests.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/helper/DBHelperTests.class new file mode 100644 index 0000000..d3a5819 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/helper/DBHelperTests.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorExtractor.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorExtractor.class new file mode 100644 index 0000000..4fb6508 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorExtractor.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorRowMapper.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorRowMapper.class new file mode 100644 index 0000000..2e78a93 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorRowMapper.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorExtractorTests.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorExtractorTests.class new file mode 100644 index 0000000..12123b1 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorExtractorTests.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorRowMapperTests.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorRowMapperTests.class new file mode 100644 index 0000000..ce0b1f7 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorRowMapperTests.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorExtractor.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorExtractor.class new file mode 100644 index 0000000..40cadfc Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorExtractor.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorRowMapper.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorRowMapper.class new file mode 100644 index 0000000..7e9b920 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorRowMapper.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/SampleDataHelper.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/SampleDataHelper.class new file mode 100644 index 0000000..63a6295 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/interfaces/SampleDataHelper.class differ diff --git a/bin/classes/com/perfectworldprogramming/mobile/orm/test/reflection/DomainClassAnalyzerTests.class b/bin/classes/com/perfectworldprogramming/mobile/orm/test/reflection/DomainClassAnalyzerTests.class new file mode 100644 index 0000000..46356f2 Binary files /dev/null and b/bin/classes/com/perfectworldprogramming/mobile/orm/test/reflection/DomainClassAnalyzerTests.class differ diff --git a/bin/org/springframework/mobile/orm/AndroidSQLiteTemplate.class b/bin/org/springframework/mobile/orm/AndroidSQLiteTemplate.class new file mode 100644 index 0000000..b6c0f52 Binary files /dev/null and b/bin/org/springframework/mobile/orm/AndroidSQLiteTemplate.class differ diff --git a/bin/org/springframework/mobile/orm/CursorAdapter.class b/bin/org/springframework/mobile/orm/CursorAdapter.class new file mode 100644 index 0000000..592a9f5 Binary files /dev/null and b/bin/org/springframework/mobile/orm/CursorAdapter.class differ diff --git a/bin/org/springframework/mobile/orm/Main.class b/bin/org/springframework/mobile/orm/Main.class new file mode 100644 index 0000000..8e21a27 Binary files /dev/null and b/bin/org/springframework/mobile/orm/Main.class differ diff --git a/bin/org/springframework/mobile/orm/R$attr.class b/bin/org/springframework/mobile/orm/R$attr.class new file mode 100644 index 0000000..403bf6a Binary files /dev/null and b/bin/org/springframework/mobile/orm/R$attr.class differ diff --git a/bin/org/springframework/mobile/orm/R$drawable.class b/bin/org/springframework/mobile/orm/R$drawable.class new file mode 100644 index 0000000..2d54064 Binary files /dev/null and b/bin/org/springframework/mobile/orm/R$drawable.class differ diff --git a/bin/org/springframework/mobile/orm/R$layout.class b/bin/org/springframework/mobile/orm/R$layout.class new file mode 100644 index 0000000..2f07745 Binary files /dev/null and b/bin/org/springframework/mobile/orm/R$layout.class differ diff --git a/bin/org/springframework/mobile/orm/R$string.class b/bin/org/springframework/mobile/orm/R$string.class new file mode 100644 index 0000000..4fd1766 Binary files /dev/null and b/bin/org/springframework/mobile/orm/R$string.class differ diff --git a/bin/org/springframework/mobile/orm/R.class b/bin/org/springframework/mobile/orm/R.class new file mode 100644 index 0000000..68fee92 Binary files /dev/null and b/bin/org/springframework/mobile/orm/R.class differ diff --git a/bin/org/springframework/mobile/orm/annotations/Column.class b/bin/org/springframework/mobile/orm/annotations/Column.class new file mode 100644 index 0000000..d832ca2 Binary files /dev/null and b/bin/org/springframework/mobile/orm/annotations/Column.class differ diff --git a/bin/org/springframework/mobile/orm/annotations/ColumnSetter.class b/bin/org/springframework/mobile/orm/annotations/ColumnSetter.class new file mode 100644 index 0000000..579918d Binary files /dev/null and b/bin/org/springframework/mobile/orm/annotations/ColumnSetter.class differ diff --git a/bin/org/springframework/mobile/orm/annotations/ColumnType.class b/bin/org/springframework/mobile/orm/annotations/ColumnType.class new file mode 100644 index 0000000..db560fc Binary files /dev/null and b/bin/org/springframework/mobile/orm/annotations/ColumnType.class differ diff --git a/bin/org/springframework/mobile/orm/annotations/ForeignKey.class b/bin/org/springframework/mobile/orm/annotations/ForeignKey.class new file mode 100644 index 0000000..4377834 Binary files /dev/null and b/bin/org/springframework/mobile/orm/annotations/ForeignKey.class differ diff --git a/bin/org/springframework/mobile/orm/annotations/PrimaryKey.class b/bin/org/springframework/mobile/orm/annotations/PrimaryKey.class new file mode 100644 index 0000000..c02d36d Binary files /dev/null and b/bin/org/springframework/mobile/orm/annotations/PrimaryKey.class differ diff --git a/bin/org/springframework/mobile/orm/annotations/Transient.class b/bin/org/springframework/mobile/orm/annotations/Transient.class new file mode 100644 index 0000000..1a1092f Binary files /dev/null and b/bin/org/springframework/mobile/orm/annotations/Transient.class differ diff --git a/bin/org/springframework/mobile/orm/creator/CreateStatementGenerator.class b/bin/org/springframework/mobile/orm/creator/CreateStatementGenerator.class new file mode 100644 index 0000000..3fb563d Binary files /dev/null and b/bin/org/springframework/mobile/orm/creator/CreateStatementGenerator.class differ diff --git a/bin/org/springframework/mobile/orm/creator/SQLLiteCreateStatementGenerator.class b/bin/org/springframework/mobile/orm/creator/SQLLiteCreateStatementGenerator.class new file mode 100644 index 0000000..bdaff82 Binary files /dev/null and b/bin/org/springframework/mobile/orm/creator/SQLLiteCreateStatementGenerator.class differ diff --git a/bin/org/springframework/mobile/orm/exception/DataAccessException.class b/bin/org/springframework/mobile/orm/exception/DataAccessException.class new file mode 100644 index 0000000..dfdf190 Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/DataAccessException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/EmptySQLStatementException.class b/bin/org/springframework/mobile/orm/exception/EmptySQLStatementException.class new file mode 100644 index 0000000..595886c Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/EmptySQLStatementException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/ExtraResultsException.class b/bin/org/springframework/mobile/orm/exception/ExtraResultsException.class new file mode 100644 index 0000000..72aac8c Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/ExtraResultsException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/InvalidCursorExtractorException.class b/bin/org/springframework/mobile/orm/exception/InvalidCursorExtractorException.class new file mode 100644 index 0000000..68ee643 Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/InvalidCursorExtractorException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/InvalidCursorRowMapperException.class b/bin/org/springframework/mobile/orm/exception/InvalidCursorRowMapperException.class new file mode 100644 index 0000000..1df1003 Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/InvalidCursorRowMapperException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/InvalidQueryTypeException.class b/bin/org/springframework/mobile/orm/exception/InvalidQueryTypeException.class new file mode 100644 index 0000000..70d233e Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/InvalidQueryTypeException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/NoPrimaryKeyFieldException.class b/bin/org/springframework/mobile/orm/exception/NoPrimaryKeyFieldException.class new file mode 100644 index 0000000..510f4a9 Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/NoPrimaryKeyFieldException.class differ diff --git a/bin/org/springframework/mobile/orm/exception/NoRowsReturnedException.class b/bin/org/springframework/mobile/orm/exception/NoRowsReturnedException.class new file mode 100644 index 0000000..f8c8ebc Binary files /dev/null and b/bin/org/springframework/mobile/orm/exception/NoRowsReturnedException.class differ diff --git a/bin/org/springframework/mobile/orm/helper/AndroidSQLLiteOpenHelper.class b/bin/org/springframework/mobile/orm/helper/AndroidSQLLiteOpenHelper.class new file mode 100644 index 0000000..66af0b7 Binary files /dev/null and b/bin/org/springframework/mobile/orm/helper/AndroidSQLLiteOpenHelper.class differ diff --git a/bin/org/springframework/mobile/orm/helper/DBHelper.class b/bin/org/springframework/mobile/orm/helper/DBHelper.class new file mode 100644 index 0000000..d99992f Binary files /dev/null and b/bin/org/springframework/mobile/orm/helper/DBHelper.class differ diff --git a/bin/org/springframework/mobile/orm/interfaces/CursorExtractor.class b/bin/org/springframework/mobile/orm/interfaces/CursorExtractor.class new file mode 100644 index 0000000..8abc4e9 Binary files /dev/null and b/bin/org/springframework/mobile/orm/interfaces/CursorExtractor.class differ diff --git a/bin/org/springframework/mobile/orm/interfaces/CursorRowMapper.class b/bin/org/springframework/mobile/orm/interfaces/CursorRowMapper.class new file mode 100644 index 0000000..b80fba0 Binary files /dev/null and b/bin/org/springframework/mobile/orm/interfaces/CursorRowMapper.class differ diff --git a/bin/org/springframework/mobile/orm/interfaces/JdbcOperations.class b/bin/org/springframework/mobile/orm/interfaces/JdbcOperations.class new file mode 100644 index 0000000..da52418 Binary files /dev/null and b/bin/org/springframework/mobile/orm/interfaces/JdbcOperations.class differ diff --git a/bin/org/springframework/mobile/orm/reflection/DomainClassAnalyzer.class b/bin/org/springframework/mobile/orm/reflection/DomainClassAnalyzer.class new file mode 100644 index 0000000..1db3573 Binary files /dev/null and b/bin/org/springframework/mobile/orm/reflection/DomainClassAnalyzer.class differ diff --git a/bin/org/springframework/mobile/orm/test/AndroidSQLiteTemplateTests.class b/bin/org/springframework/mobile/orm/test/AndroidSQLiteTemplateTests.class new file mode 100644 index 0000000..f919676 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/AndroidSQLiteTemplateTests.class differ diff --git a/bin/org/springframework/mobile/orm/test/Main.class b/bin/org/springframework/mobile/orm/test/Main.class new file mode 100644 index 0000000..0f6acc1 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/Main.class differ diff --git a/bin/org/springframework/mobile/orm/test/MyInstrumentationTestRunner.class b/bin/org/springframework/mobile/orm/test/MyInstrumentationTestRunner.class new file mode 100644 index 0000000..091461c Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/MyInstrumentationTestRunner.class differ diff --git a/bin/org/springframework/mobile/orm/test/creator/TableCreatorTest.class b/bin/org/springframework/mobile/orm/test/creator/TableCreatorTest.class new file mode 100644 index 0000000..c6e8655 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/creator/TableCreatorTest.class differ diff --git a/bin/org/springframework/mobile/orm/test/domain/Account.class b/bin/org/springframework/mobile/orm/test/domain/Account.class new file mode 100644 index 0000000..32bd3ac Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/domain/Account.class differ diff --git a/bin/org/springframework/mobile/orm/test/domain/Address.class b/bin/org/springframework/mobile/orm/test/domain/Address.class new file mode 100644 index 0000000..c592080 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/domain/Address.class differ diff --git a/bin/org/springframework/mobile/orm/test/domain/NoPKDomain.class b/bin/org/springframework/mobile/orm/test/domain/NoPKDomain.class new file mode 100644 index 0000000..8147438 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/domain/NoPKDomain.class differ diff --git a/bin/org/springframework/mobile/orm/test/domain/Person.class b/bin/org/springframework/mobile/orm/test/domain/Person.class new file mode 100644 index 0000000..a123127 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/domain/Person.class differ diff --git a/bin/org/springframework/mobile/orm/test/domain/WrongTypeMappedDomain.class b/bin/org/springframework/mobile/orm/test/domain/WrongTypeMappedDomain.class new file mode 100644 index 0000000..0701e9a Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/domain/WrongTypeMappedDomain.class differ diff --git a/bin/org/springframework/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.class b/bin/org/springframework/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.class new file mode 100644 index 0000000..eca0942 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.class differ diff --git a/bin/org/springframework/mobile/orm/test/helper/DBHelperTests.class b/bin/org/springframework/mobile/orm/test/helper/DBHelperTests.class new file mode 100644 index 0000000..e42d386 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/helper/DBHelperTests.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/AddressCursorExtractor.class b/bin/org/springframework/mobile/orm/test/interfaces/AddressCursorExtractor.class new file mode 100644 index 0000000..31557f3 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/AddressCursorExtractor.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/AddressCursorRowMapper.class b/bin/org/springframework/mobile/orm/test/interfaces/AddressCursorRowMapper.class new file mode 100644 index 0000000..2ced2dd Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/AddressCursorRowMapper.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/CursorExtractorTests.class b/bin/org/springframework/mobile/orm/test/interfaces/CursorExtractorTests.class new file mode 100644 index 0000000..c22ab63 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/CursorExtractorTests.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/CursorRowMapperTests.class b/bin/org/springframework/mobile/orm/test/interfaces/CursorRowMapperTests.class new file mode 100644 index 0000000..e7b4025 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/CursorRowMapperTests.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/PersonCursorExtractor.class b/bin/org/springframework/mobile/orm/test/interfaces/PersonCursorExtractor.class new file mode 100644 index 0000000..c8115e3 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/PersonCursorExtractor.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/PersonCursorRowMapper.class b/bin/org/springframework/mobile/orm/test/interfaces/PersonCursorRowMapper.class new file mode 100644 index 0000000..2970350 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/PersonCursorRowMapper.class differ diff --git a/bin/org/springframework/mobile/orm/test/interfaces/SampleDataHelper.class b/bin/org/springframework/mobile/orm/test/interfaces/SampleDataHelper.class new file mode 100644 index 0000000..35cb8b2 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/interfaces/SampleDataHelper.class differ diff --git a/bin/org/springframework/mobile/orm/test/reflection/DomainClassAnalyzerTests.class b/bin/org/springframework/mobile/orm/test/reflection/DomainClassAnalyzerTests.class new file mode 100644 index 0000000..da5ad75 Binary files /dev/null and b/bin/org/springframework/mobile/orm/test/reflection/DomainClassAnalyzerTests.class differ diff --git a/bin/resources.ap_ b/bin/resources.ap_ new file mode 100644 index 0000000..52fde5e Binary files /dev/null and b/bin/resources.ap_ differ diff --git a/gen/org/springframework/mobile/orm/R.java b/gen/com/perfectworldprogramming/mobile/orm/R.java similarity index 92% rename from gen/org/springframework/mobile/orm/R.java rename to gen/com/perfectworldprogramming/mobile/orm/R.java index 133ec9f..3e1d7d0 100644 --- a/gen/org/springframework/mobile/orm/R.java +++ b/gen/com/perfectworldprogramming/mobile/orm/R.java @@ -5,7 +5,7 @@ * should not be modified by hand. */ -package org.springframework.mobile.orm; +package com.perfectworldprogramming.mobile.orm; public final class R { public static final class attr { diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 0000000..12dd039 --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,36 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..ea89160 --- /dev/null +++ b/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-8 diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..8074c4c Binary files /dev/null and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..1095584 Binary files /dev/null and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..a07c69f Binary files /dev/null and b/res/drawable-mdpi/icon.png differ diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 0000000..3a5f117 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..631af88 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Hello World, Main! + AndroidSQLLiteOrm + diff --git a/src/com/perfectworldprogramming/mobile/orm/AndroidSQLiteTemplate.java b/src/com/perfectworldprogramming/mobile/orm/AndroidSQLiteTemplate.java new file mode 100644 index 0000000..1a39051 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/AndroidSQLiteTemplate.java @@ -0,0 +1,327 @@ +package com.perfectworldprogramming.mobile.orm; + +import java.util.List; + +import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; +import com.perfectworldprogramming.mobile.orm.exception.EmptySQLStatementException; +import com.perfectworldprogramming.mobile.orm.exception.NoRowsReturnedException; +import com.perfectworldprogramming.mobile.orm.interfaces.CursorExtractor; +import com.perfectworldprogramming.mobile.orm.interfaces.CursorRowMapper; +import com.perfectworldprogramming.mobile.orm.interfaces.JdbcOperations; +import com.perfectworldprogramming.mobile.orm.reflection.DomainClassAnalyzer; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDoneException; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; + +/** + * For queries that return Cursors will use the CursorAdapter class to convert the Cursor into your Domain object, either via + * Annotations you put in your domain objects to map to the tables, or via CursorRowMapper or CursorExtractor implementations. + * Either approach, the CursorAdapter will close the Cursor after it has completed conversion. No need to close Cursors yourself. + * + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 10:07 PM + */ +public class AndroidSQLiteTemplate implements JdbcOperations { + private SQLiteDatabase sqLiteDatabase; + private DomainClassAnalyzer domainClassAnalyzer = new DomainClassAnalyzer(); + private CursorAdapter cursorAdapter = new CursorAdapter(); + + public AndroidSQLiteTemplate(SQLiteDatabase sqLiteDatabase) { + this.sqLiteDatabase = sqLiteDatabase; + } + + @Override + public long insert(Object object) { + ContentValues values = domainClassAnalyzer.createContentValues(object); + long id = sqLiteDatabase.insert(object.getClass().getSimpleName(), + null, values); + // the insert method used above doesn't through an exception if it can't insert, but returns -1 as the result. + if (id != -1) { + domainClassAnalyzer.setIdToNewObject(object, id); + } else { + throw new DataAccessException("Unable to insert new record for object: " + object); + } + return id; + } + + @Override + public long insert(String sql, Object... args) { + sql = replaceParametersInStatement(sql, args); + SQLiteStatement statement = null; + Long results; + try { + statement = sqLiteDatabase.compileStatement(sql); + results = statement.executeInsert(); + } catch (SQLException se) { + throw new DataAccessException(se.getMessage()); + } finally { + if (statement != null) { + statement.close(); + } + } + return results; + } + + @Override + public long update(Object object) { + ContentValues values = domainClassAnalyzer.createContentValues(object); + String whereClause = domainClassAnalyzer.getPrimaryKeyFieldName(object.getClass()) + " = ?"; + String[] whereArgs = {String.valueOf(domainClassAnalyzer.getIdFromObject(object))}; + + return sqLiteDatabase.update(object.getClass().getSimpleName(), + values, whereClause, whereArgs); + } + + @Override + public void update(String sql, Object... args) { + sql = replaceParametersInStatement(sql, args); + SQLiteStatement statement = null; + try { + statement = sqLiteDatabase.compileStatement(sql); + statement.execute(); + } catch (SQLException se) { + throw new DataAccessException(se.getMessage()); + } finally { + if (statement != null) { + statement.close(); + } + } + } + + @Override + public long delete(Object object) { + if (object == null) { + throw new DataAccessException("Please supply a non null domain object to delete(Object) method"); + } + String whereClause = domainClassAnalyzer.getPrimaryKeyFieldName(object.getClass()) + " = ?"; + String[] whereArgs = {String.valueOf(domainClassAnalyzer.getIdFromObject(object))}; + long results = -1; + try { + results = sqLiteDatabase.delete(object.getClass().getSimpleName(), + whereClause, whereArgs); + if (results == 0) { + throw new DataAccessException("unable to delete " + object.getClass().getName() + ". Most likely the Primary Key id value does not exist in the database."); + } + } catch (SQLiteException sle) { + + } + return results; + } + + @Override + public long delete(String table, String columnName, String columnValue) { + if (table == null || columnName == null || columnValue == null || "".equals(table)|| "".equals(columnName)|| "".equals(table)) { + throw new DataAccessException("Please supply tableName, columnName and columnValue in order to delete any data"); + } + String[] args = {columnValue}; + long results = -1; + try { + results = sqLiteDatabase.delete(table, "" + columnName + "=?", args); + } catch (SQLiteException sle) { + throw new DataAccessException("Call to delete(String, String, String) failed. Please check you are passing in correct tableName, columnName and value to this method"); + } + return results; + } + + @Override + public void delete(String sql, Object... args) { + update(sql, args); + } + + @Override + public int queryForInt(String sql, Object... args) { + return (int)queryForLong(sql, args); + } + + @Override + public long queryForLong(String sql, Object... args) { + sql = replaceParametersInStatement(sql, args); + SQLiteStatement statement = null; + long results; + try { + statement = sqLiteDatabase.compileStatement(sql); + results = statement.simpleQueryForLong(); + } catch (SQLiteDoneException se) { + throw new NoRowsReturnedException("Query For Long/Int did not return any rows for sql: " + sql); + } finally { + if (statement != null) { + statement.close(); + } + } + return results; + } + + @Override + public String queryForString(String sql, Object... args) { + sql = replaceParametersInStatement(sql, args); + SQLiteStatement statement = null; + String results; + try { + statement = sqLiteDatabase.compileStatement(sql); + results = statement.simpleQueryForString(); + } catch (SQLiteDoneException se) { + throw new NoRowsReturnedException("Query For String did not return any rows for sql: " + sql); + } catch (SQLException e) { + throw new DataAccessException(e.getMessage()); + } finally { + if (statement != null) { + statement.close(); + } + } + return results; + } + + //** All Implementations below call their corresponding executeFor* private methods for a more central exception handling + @Override + public T queryForObject(String sql, CursorRowMapper cursorRowMapper, Object... args) { + if (cursorRowMapper == null) { + throw new IllegalArgumentException(); + } + sql = replaceParametersInStatement(sql, args); + return executeForSingleObject(sql, cursorRowMapper); + } + + @Override + public T queryForObject(String sql, Class clazz, Object... args) { + sql = replaceParametersInStatement(sql, args); + T t = executeForSingleObject(sql, clazz); + if (t == null) { + throw new DataAccessException("QueryForObject returned no results for query: " + sql); + } + return t; + } + + @Override + public T queryForObject(String sql, CursorExtractor cursorExtractor, Object... args) { + if (cursorExtractor == null) { + throw new IllegalArgumentException(); + } + sql = replaceParametersInStatement(sql, args); + return executeForSingleObject(sql, cursorExtractor); + } + + @Override + public T findById(Class clazz, Long id) { + String sql = generateStatementById(clazz, id); + return executeForSingleObject(sql, clazz); + } + + @Override + public List query(String sql, Class clazz, Object... args) { + sql = replaceParametersInStatement(sql, args); + return executeForList(sql, clazz); + } + + @Override + public List query(String sql, CursorRowMapper cursorRowMapper, Object... args) { + if (cursorRowMapper == null) { + throw new IllegalArgumentException(); + } + sql = replaceParametersInStatement(sql, args); + return executeForList(sql, cursorRowMapper); + } + + private T executeForSingleObject(String sql, CursorExtractor cursorExtractor) { + if (sql == null || "".equals(sql)) { + throw new EmptySQLStatementException(); + } + + if (cursorExtractor == null) { + throw new IllegalArgumentException(); + } + + Cursor cursor = null; + try { + cursor = sqLiteDatabase.rawQuery(sql, null); + return cursorAdapter.adaptFromCursor(cursor, cursorExtractor); + } catch (SQLiteException se) { + throw new DataAccessException(se.getMessage()); + } + } + + private T executeForSingleObject(String sql, CursorRowMapper cursorRowMapper) { + if (cursorRowMapper == null) { + throw new IllegalArgumentException(); + } + Cursor cursor = null; + try { + cursor = sqLiteDatabase.rawQuery(sql, null); + return cursorAdapter.adaptFromCursor(cursor, cursorRowMapper); + } catch (SQLiteException se) { + throw new DataAccessException(se.getMessage()); + } + } + + private T executeForSingleObject(String sql, Class clazz) { + Cursor cursor = null; + try { + cursor = sqLiteDatabase.rawQuery(sql, null); + return cursorAdapter.adaptFromCursor(cursor, clazz); + } catch (SQLiteException se) { + throw new DataAccessException(se.getMessage()); + } + } + + private List executeForList(String sql, CursorRowMapper cursorRowMapper) { + if (cursorRowMapper == null) { + throw new IllegalArgumentException(); + } + Cursor cursor = null; + try { + cursor = sqLiteDatabase.rawQuery(sql, null); + return cursorAdapter.adaptListFromCursor(cursor, cursorRowMapper); + } catch (SQLiteException se) { + throw new DataAccessException(se.getMessage()); + } catch (Exception e) { + throw new DataAccessException("Unable to execute query. Please check your sql for incorrect sql grammer."); + } + } + + private List executeForList(String sql, Class clazz) { + Cursor cursor = null; + try { + cursor = sqLiteDatabase.rawQuery(sql, null); + return cursorAdapter.adaptListFromCursor(cursor, clazz); + } catch (SQLiteException se) { + throw new DataAccessException(se.getMessage()); + } + } + + private String generateStatementById(Class clazz, Long id) { + StringBuilder selectStatement = new StringBuilder("SELECT * FROM "); + selectStatement.append(clazz.getSimpleName()); + selectStatement.append(" WHERE "); + selectStatement.append(domainClassAnalyzer.getPrimaryKeyFieldName(clazz)); + selectStatement.append(" = "); + selectStatement.append(id); + + return selectStatement.toString(); + } + + private String replaceParametersInStatement(String sql, Object... args) { + if (sql == null || "".equals(sql)) { + throw new EmptySQLStatementException(); + } + + if (args != null) { + for (Object arg: args) { + if (arg != null) { + String replaceMe = "[/?]"; + sql = sql.replaceFirst(replaceMe, arg.toString()); + } + } + } + if (sql.contains("?")) { + throw new DataAccessException("Not all Query parameters have been set, please set all values for query to execute"); + } + + return sql; + } + +} diff --git a/src/com/perfectworldprogramming/mobile/orm/CursorAdapter.java b/src/com/perfectworldprogramming/mobile/orm/CursorAdapter.java new file mode 100644 index 0000000..65c599a --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/CursorAdapter.java @@ -0,0 +1,231 @@ +package com.perfectworldprogramming.mobile.orm; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ForeignKey; +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; +import com.perfectworldprogramming.mobile.orm.annotations.Transient; +import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; +import com.perfectworldprogramming.mobile.orm.exception.ExtraResultsException; +import com.perfectworldprogramming.mobile.orm.exception.InvalidCursorExtractorException; +import com.perfectworldprogramming.mobile.orm.exception.InvalidCursorRowMapperException; +import com.perfectworldprogramming.mobile.orm.interfaces.CursorExtractor; +import com.perfectworldprogramming.mobile.orm.interfaces.CursorRowMapper; + +import android.database.Cursor; + +/** + * User: Mark Spritzler + * Date: 3/12/11 + * Time: 9:45 PM + */ +public class CursorAdapter { + + /** + * convert a Cursor with a multitude of date rows and return a List of instances of Class + * That is mapped to the corresponding database table that the Cursor has data from + * + * @param cursor returned data from the database query + * @param clazz Domain object Class to put all the data from the cursor + * @param Domain Object type + * @return List returns a list of populated domain objects based on the cursor data. + */ + public List adaptListFromCursor(Cursor cursor, Class clazz) { + List results = new ArrayList(); + if (cursor.getCount() > 0) { + results = getValuesFromCursor(cursor, clazz); + cursor.close(); + } + return results; + } + + /** + * convert a Cursor with a multitude of data rows and return a List of instances by calling + * the CursorRowMapper passed in. This will loop through all the rows of the Cursor and pass + * each row to the CursorRowMapper. + * + * @param cursor returned data from the database query. + * @param cursorRowMapper CursorRowMapper that implements mapRow to convert a row in the cursor to a instance + * @param Domain Object type the CursorRowMapper maps + * @return List returns a list of populated domain objects based on the cursor data. + */ + public List adaptListFromCursor(Cursor cursor, CursorRowMapper cursorRowMapper) { + List values = new ArrayList(); + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + try { + T newInstance = cursorRowMapper.mapRow(cursor, cursor.getPosition()); + values.add(newInstance); + } catch (IllegalStateException ise) { + if (!cursor.isClosed()) { + cursor.close(); + } + throw new InvalidCursorRowMapperException(cursorRowMapper.getClass()); + } + } while (cursor.moveToNext()); + } + if (!cursor.isClosed()) { + cursor.close(); + } + } + return values; + } + + /** + * Returns a Single Domain Object from the Cursor's first row. All other rows will be ignored. + * @param cursor returned data from the database query. + * @param clazz Domain object Class to put all the data from the cursor + * @param Domain Object type of the Class + * @return T domain object populated with data from the first row in the Cursor + */ + public T adaptFromCursor(Cursor cursor, Class clazz) { + T newInstance = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + newInstance = getSingleObjectValuesFromCursor(cursor, clazz); + cursor.close(); + } + } + return newInstance; + } + + public T adaptFromCursor(Cursor cursor, CursorRowMapper cursorRowMapper) { + T newInstance = null; + if (cursor != null) { + if(cursor.getCount() != 1) { + throw new ExtraResultsException(cursor.getCount()); + } + + if (cursor.moveToFirst()) { + try { + newInstance = cursorRowMapper.mapRow(cursor, 1); + } catch (IllegalStateException ise) { + throw new InvalidCursorRowMapperException(cursorRowMapper.getClass()); + } finally { + if (!cursor.isClosed()) { + cursor.close(); + } + } + } + } + return newInstance; + } + + /** + * Converts the data from the Cursor into a Domain object based on the CursorExtractor callback. + * + * A CursorExtractor receives the entire Cursor, and is responsible for looping through the + * dataset and created the more complex domain object tree graph. + * + * @param cursor returned data from the database query. + * @param cursorExtractor Callback interface that receives the entire Cursor + * @param Domain Object type of the Class + * @return T Domain Object fully populated with data from the Cursor + */ + public T adaptFromCursor(Cursor cursor, CursorExtractor cursorExtractor) { + T result = null; + try { + result = cursorExtractor.extractData(cursor); + } catch (IllegalStateException ise) { + throw new InvalidCursorExtractorException(cursorExtractor.getClass()); + } finally { + if (!cursor.isClosed()) { + cursor.close(); + } + } + return result; + } + + private List getValuesFromCursor(Cursor cursor, Class clazz) { + List values = new ArrayList(); + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + T newInstance = getSingleObjectValuesFromCursor(cursor, clazz); + values.add(newInstance); + } while (cursor.moveToNext()); + } + cursor.close(); + } + return values; + } + + private T getSingleObjectValuesFromCursor(Cursor cursor, Class clazz) { + T newInstance = null; + try { + newInstance = clazz.newInstance(); + setFieldValues(clazz, newInstance, cursor); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + return newInstance; + } + + private void setFieldValues(Class clazz, T object, Cursor cursor) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + setFieldValue(field, object, cursor); + } + } + + private void setFieldValue(Field fieldToSet, T object, Cursor cursor) { + + //Skip over @Transient and @ForeignKey fields + if (fieldToSet.isAnnotationPresent(Transient.class) || + fieldToSet.isAnnotationPresent(ForeignKey.class)) { + return; + } + String columnName = getColumnName(fieldToSet); + + int columnIndex = cursor.getColumnIndex(columnName); + + fieldToSet.setAccessible(true); + Class clazz = fieldToSet.getType(); + Object value = getValue(clazz, cursor, columnIndex); + if (value != null) { + try { + fieldToSet.set(object, value); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + private String getColumnName(Field field) { + String columnName; + Column column = field.getAnnotation(Column.class); + if (column == null) { + PrimaryKey key = field.getAnnotation(PrimaryKey.class); + columnName = key.value(); + } else { + columnName = column.value(); + } + return columnName; + } + + private Object getValue(Class clazz, Cursor cursor, int columnIndex) { + if (cursor.getColumnCount() <= columnIndex || columnIndex < 0) { + throw new DataAccessException("Column index " + columnIndex + " does not exist"); + } + if (clazz.getName().endsWith("Integer") || clazz.getName().endsWith("int")) { + return cursor.getInt(columnIndex); + } else if (clazz.getName().endsWith("Long") || clazz.getName().endsWith("long")) { + return cursor.getLong(columnIndex); + } else if (clazz.getName().endsWith("Double") || clazz.getName().endsWith("double")) { + return cursor.getDouble(columnIndex); + } else if (clazz.getName().endsWith("Float") || clazz.getName().endsWith("float")) { + return cursor.getFloat(columnIndex); + } else if (clazz.getName().endsWith("String")) { + return cursor.getString(columnIndex); + } + return null; + } + +} diff --git a/src/com/perfectworldprogramming/mobile/orm/Main.java b/src/com/perfectworldprogramming/mobile/orm/Main.java new file mode 100644 index 0000000..170d393 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/Main.java @@ -0,0 +1,19 @@ +package com.perfectworldprogramming.mobile.orm; + +import com.perfectworldprogramming.mobile.orm.R; + +import com.perfectworldprogramming.mobile.orm.helper.AndroidSQLLiteOpenHelper; + +import android.app.Activity; +import android.os.Bundle; + +public class Main extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + AndroidSQLLiteOpenHelper dbHelper = new AndroidSQLLiteOpenHelper(this.getApplicationContext(), null, "ormtest", 1); + dbHelper.getWritableDatabase(); + } +} \ No newline at end of file diff --git a/src/com/perfectworldprogramming/mobile/orm/annotations/Column.java b/src/com/perfectworldprogramming/mobile/orm/annotations/Column.java new file mode 100644 index 0000000..a14c96d --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/annotations/Column.java @@ -0,0 +1,54 @@ +package com.perfectworldprogramming.mobile.orm.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to place of Domain object properties to map them to a field + * in a database table. + * + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 8:36 PM + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ColumnSetter() +public @interface Column { + /** + * Column name in the database. This will be used in the CREATE statements + * and for setting the domain property to the value of the column in the database. + * + * @return String field name in the database + */ + public String value() default ""; + + /** + * Should the column be unique and not allow duplicates. + * + * default value is false, allowing for duplicates in the database. + * + * @return boolean + */ + public boolean unique() default false; + + /** + * Should the column allow nulls. True allows nulls, False does not allow nulls + * + * default value is true, allowing for nulls. + * + * @return boolean + */ + public boolean nullable() default true; + + /** + * Used to determine the field type to use in the CREATE statement. + * + * @return ColumnType + */ + public ColumnType type() default ColumnType.TEXT; +} diff --git a/src/com/perfectworldprogramming/mobile/orm/annotations/ColumnSetter.java b/src/com/perfectworldprogramming/mobile/orm/annotations/ColumnSetter.java new file mode 100644 index 0000000..e3ea34a --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/annotations/ColumnSetter.java @@ -0,0 +1,17 @@ +package com.perfectworldprogramming.mobile.orm.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * User: Mark Spritzler + * Date: 3/23/11 + * Time: 6:33 PM + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ColumnSetter { + public String value() default ""; +} diff --git a/src/com/perfectworldprogramming/mobile/orm/annotations/ColumnType.java b/src/com/perfectworldprogramming/mobile/orm/annotations/ColumnType.java new file mode 100644 index 0000000..9493397 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/annotations/ColumnType.java @@ -0,0 +1,13 @@ +package com.perfectworldprogramming.mobile.orm.annotations; + +/** + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 8:42 PM + */ +public enum ColumnType { + TEXT, + INTEGER, + REAL, + BLOB +} diff --git a/src/com/perfectworldprogramming/mobile/orm/annotations/ForeignKey.java b/src/com/perfectworldprogramming/mobile/orm/annotations/ForeignKey.java new file mode 100644 index 0000000..97a8eed --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/annotations/ForeignKey.java @@ -0,0 +1,22 @@ +package com.perfectworldprogramming.mobile.orm.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to put on a ManyToOne association between domain objects. + * + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 9:21 PM + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ColumnSetter +public @interface ForeignKey { + public String value() default ""; +} diff --git a/src/com/perfectworldprogramming/mobile/orm/annotations/PrimaryKey.java b/src/com/perfectworldprogramming/mobile/orm/annotations/PrimaryKey.java new file mode 100644 index 0000000..d6e4fda --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/annotations/PrimaryKey.java @@ -0,0 +1,26 @@ +package com.perfectworldprogramming.mobile.orm.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to put on the Primary Key field of a domain object. + * + * It is best to make the property to be a Long. + * + * The generated Primary Key field in the database will be an auto-increment field. + * + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 9:44 PM + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ColumnSetter +public @interface PrimaryKey { + public String value() default ""; +} diff --git a/src/com/perfectworldprogramming/mobile/orm/annotations/Transient.java b/src/com/perfectworldprogramming/mobile/orm/annotations/Transient.java new file mode 100644 index 0000000..7fb5856 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/annotations/Transient.java @@ -0,0 +1,18 @@ +package com.perfectworldprogramming.mobile.orm.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * User: Mark Spritzler + * Date: 3/17/11 + * Time: 4:30 PM + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Transient { +} diff --git a/src/com/perfectworldprogramming/mobile/orm/creator/CreateStatementGenerator.java b/src/com/perfectworldprogramming/mobile/orm/creator/CreateStatementGenerator.java new file mode 100644 index 0000000..b731882 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/creator/CreateStatementGenerator.java @@ -0,0 +1,47 @@ +package com.perfectworldprogramming.mobile.orm.creator; + +import java.util.List; + +/** + * User: Mark Spritzler + * Date: 3/19/11 + * Time: 2:53 PM + */ +public interface CreateStatementGenerator { + + /** + * List of Create statements to run on the database to create tables + * + * @return List List of Create statements to run on the database to create tables. + */ + public List getCreateStatements(); + + /** + * Creates a CREATE SQL Statement for the Class that is passed in as a parameter. + * The Class is mapped with the Spring Mobile ORM annotations. Without those + * annotations this method should return + * + * The returned CREATE statement can be run by an Android SQLLiteDatabaseHelper class to create + * the tables when the SQLLiteDatabaseHelper class' onCreate method is called. + * + * @param clazz domain class + * @return String Create SQL Statement to run on the database to create a database table. + */ + public String createCreateStatement(Class clazz); + + /** + * * Creates a CREATE SQL Statement with 'IF NOT EXISTS' for the Class that is passed in as a parameter. + * The Class is mapped with the Spring Mobile ORM annotations. Without those + * annotations this method should return + * + * The returned CREATE statement can be run by an Android SQLLiteDatabaseHelper class to create + * the tables when the SQLLiteDatabaseHelper class' onUpdate method is called. + * + * This will create new tables added in the update and also let us know what already exists to update the fields + * + * @param clazz domain class + * @return String Create SQL Statement to run on the database to create a database table. + */ + public String createCreateIfNotExistsStatement(Class clazz); + +} diff --git a/src/com/perfectworldprogramming/mobile/orm/creator/SQLLiteCreateStatementGenerator.java b/src/com/perfectworldprogramming/mobile/orm/creator/SQLLiteCreateStatementGenerator.java new file mode 100644 index 0000000..ba19f00 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/creator/SQLLiteCreateStatementGenerator.java @@ -0,0 +1,177 @@ +package com.perfectworldprogramming.mobile.orm.creator; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ColumnType; +import com.perfectworldprogramming.mobile.orm.annotations.ForeignKey; +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; +import com.perfectworldprogramming.mobile.orm.reflection.DomainClassAnalyzer; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * This class generates CREATE SQL statements for mapped Domain objects. + * + * There are two approaches to use this class. + *
  • Assign the classes property to an array of Class objects for each domain + * object you want create statements for. Then call the getCreateStatements method to return + * a List of Strings. Each string is a create statement for one table
  • + *
  • just call the createCreateStatement method passing in a single Class which + * returns a single Create String SQL statement
  • + *
+ * + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 8:55 PM + */ +public class SQLLiteCreateStatementGenerator implements CreateStatementGenerator { + + private static final String CREATE_TABLE = "CREATE TABLE "; + private static final String CREATE_TABLE_IF_NOT_EXISTS = "CREATE TABLE IF NOT EXISTS "; + private static final String PRIMARY_KEY = "PRIMARY KEY"; + private static final String UNIQUE = "UNIQUE"; + private static final String NOT_NULL = "NOT NULL"; + private static final String FOREIGN_KEY_ELEMENT = " INTEGER REFERENCES ()"; //" FOREIGN KEY() REFERENCES ()"; + private static final String SPACE = " "; + + private Class[] classes; + private DomainClassAnalyzer domainClassAnalyzer = new DomainClassAnalyzer(); + + /** + * Creates a SQLLiteCreateStatementGenerator without the Classes to make create SQL statements for + * Call the setClasses method to set the classes so that we can create the create statements + * or call the createCreateStatement + */ + public SQLLiteCreateStatementGenerator() {} + + /** + * Constructor that takes an array of domain {@link Class}es to create tables + * for in SQLLite create statements + * @param classes array of classes to create tables for + */ + public SQLLiteCreateStatementGenerator(Class[] classes) { + this.classes = classes; + } + + /** + * Setter method for classes property + * + * @param classes Array of Class objects for all the Domain objects to create tables for + */ + public void setClasses(Class[] classes) { + this.classes = classes; + } + + @Override + public List getCreateStatements() { + if (classes == null) { + throw new IllegalArgumentException("You must assign the domain classes to the SQLLiteCreateStatementGenerator class before generating the create statements"); + } + List createStatements = new ArrayList(classes.length); + for (Class clazz : classes) { + createStatements.add(createCreateStatement(clazz)); + } + return createStatements; + } + + @Override + public String createCreateStatement(Class clazz) { + return createStatement(clazz, CREATE_TABLE); + } + + public String createCreateIfNotExistsStatement(Class clazz) { + return createStatement(clazz, CREATE_TABLE_IF_NOT_EXISTS); + } + + /** + * Always the same code to create a create table statement. Only differences is if it needs to include IF NOT EXISTS. + */ + private String createStatement(Class clazz, String create) { + StringBuilder createStatement = new StringBuilder(create); + createStatement.append(clazz.getSimpleName()); + createStatement.append(" ("); + addAllToStatement(createStatement, clazz); + return createStatement.toString(); + } + + private void addAllToStatement(StringBuilder createStatement, Class clazz) { + Field[] fields = getFieldsToAddToStatement(clazz); + + if (fields.length == 0) { + return; + } + + for (int i = 0; i< fields.length; i++ ) { + Field field = fields[i]; + PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class); + if (primaryKey != null) { + addPrimaryKeyToStatement(createStatement, primaryKey); + } + Column column = field.getAnnotation(Column.class); + if (column != null) { + addColumnsToStatement(createStatement, column); + } + ForeignKey foreignKey = field.getAnnotation(ForeignKey.class); + if (foreignKey != null) { + addForeignKeyToStatement(createStatement, field, foreignKey); + } + + if (i != (fields.length-1)) { + createStatement.append(", "); + } else { + createStatement.append(")"); + } + } + } + + private Field[] getFieldsToAddToStatement(Class clazz) { + List fieldsToAdd = new ArrayList(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(PrimaryKey.class) || + field.isAnnotationPresent(Column.class) || + field.isAnnotationPresent(ForeignKey.class)) { + fieldsToAdd.add(field); + } + } + return fieldsToAdd.toArray(new Field[fieldsToAdd.size()]); + } + + private void addPrimaryKeyToStatement(StringBuilder createStatement, PrimaryKey primaryKey) { + String pkName = primaryKey.value(); + if (pkName.equals("")) { + pkName = "ID"; + } + createStatement.append(pkName); + createStatement.append(SPACE); + createStatement.append(ColumnType.INTEGER); + createStatement.append(SPACE); + createStatement.append(PRIMARY_KEY); + } + + private void addForeignKeyToStatement(StringBuilder createStatement, Field field, ForeignKey foreignKey) { + Class clazz = field.getType(); + String fkField = domainClassAnalyzer.getPrimaryKeyFieldName(clazz); + String foreignTable = field.getType().getSimpleName(); + String s = FOREIGN_KEY_ELEMENT.replaceAll("", foreignTable) + .replaceAll("", foreignKey.value()) + .replaceAll("", fkField); + createStatement.append(s); + } + + private void addColumnsToStatement(StringBuilder createStatement, Column column) { + createStatement.append(column.value()); + createStatement.append(SPACE); + createStatement.append(column.type().name()); + createStatement.append(SPACE); + if (column.unique()) { + createStatement.append(UNIQUE); + createStatement.append(SPACE); + } + if (!column.nullable()){ + createStatement.append(NOT_NULL); + createStatement.append(SPACE); + } + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/DataAccessException.java b/src/com/perfectworldprogramming/mobile/orm/exception/DataAccessException.java new file mode 100644 index 0000000..dee4efd --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/DataAccessException.java @@ -0,0 +1,18 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 11:26 AM + */ +public class DataAccessException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public DataAccessException(String msg) { + super(msg); + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/EmptySQLStatementException.java b/src/com/perfectworldprogramming/mobile/orm/exception/EmptySQLStatementException.java new file mode 100644 index 0000000..e36321d --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/EmptySQLStatementException.java @@ -0,0 +1,10 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +public class EmptySQLStatementException extends DataAccessException { + + private static final long serialVersionUID = 1L; + + public EmptySQLStatementException() { + super("Invalid SQL. Please supply an SQL query string."); + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/ExtraResultsException.java b/src/com/perfectworldprogramming/mobile/orm/exception/ExtraResultsException.java new file mode 100644 index 0000000..c394570 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/ExtraResultsException.java @@ -0,0 +1,15 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 11:27 AM + */ +public class ExtraResultsException extends DataAccessException { + + private static final long serialVersionUID = 1L; + + public ExtraResultsException(int numberOfResults) { + super("Expected one row returned by query but received " + numberOfResults); + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorExtractorException.java b/src/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorExtractorException.java new file mode 100644 index 0000000..58f7967 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorExtractorException.java @@ -0,0 +1,11 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +public class InvalidCursorExtractorException extends DataAccessException { + + private static final long serialVersionUID = 1L; + + public InvalidCursorExtractorException(Class class1) { + super("Invalid CursorExtractor: " + class1.getName() + ". Possible reasons are 1) query does not contain all the fields, 2) query is using the wrong tables, 3) Not navigating through the Cursor correctly, either by the cursor is empty, or has past the end of the results."); + } + +} diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorRowMapperException.java b/src/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorRowMapperException.java new file mode 100644 index 0000000..66188ef --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/InvalidCursorRowMapperException.java @@ -0,0 +1,10 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +public class InvalidCursorRowMapperException extends DataAccessException { + + private static final long serialVersionUID = 1L; + + public InvalidCursorRowMapperException(Class class1) { + super("Invalid CursorRowMapper: " + class1.getName() + ". Possible reasons are 1) query does not contain all the fields, 2) query is using the wrong tables, 3) Not navigating through the Cursor correctly, either by the cursor is empty, or has past the end of the results."); + } +} \ No newline at end of file diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/InvalidQueryTypeException.java b/src/com/perfectworldprogramming/mobile/orm/exception/InvalidQueryTypeException.java new file mode 100644 index 0000000..86b01be --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/InvalidQueryTypeException.java @@ -0,0 +1,13 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +public class InvalidQueryTypeException extends DataAccessException { + /** + * + */ + private static final long serialVersionUID = 1L; + + public InvalidQueryTypeException(String expectedType, String givenType) { + super("Invalid Query type. Was expecting a " + expectedType + " but received a " + givenType); + } + +} diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/NoPrimaryKeyFieldException.java b/src/com/perfectworldprogramming/mobile/orm/exception/NoPrimaryKeyFieldException.java new file mode 100644 index 0000000..8768bee --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/NoPrimaryKeyFieldException.java @@ -0,0 +1,13 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +public class NoPrimaryKeyFieldException extends DataAccessException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public NoPrimaryKeyFieldException(Class clazz) { + super("No primary key field found in class " + clazz.getSimpleName()); + } +} \ No newline at end of file diff --git a/src/com/perfectworldprogramming/mobile/orm/exception/NoRowsReturnedException.java b/src/com/perfectworldprogramming/mobile/orm/exception/NoRowsReturnedException.java new file mode 100644 index 0000000..8c39a99 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/exception/NoRowsReturnedException.java @@ -0,0 +1,13 @@ +package com.perfectworldprogramming.mobile.orm.exception; + +public class NoRowsReturnedException extends DataAccessException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public NoRowsReturnedException(String msg) { + super("No rows returned for query " + msg); + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/helper/AndroidSQLLiteOpenHelper.java b/src/com/perfectworldprogramming/mobile/orm/helper/AndroidSQLLiteOpenHelper.java new file mode 100644 index 0000000..fcf7e6e --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/helper/AndroidSQLLiteOpenHelper.java @@ -0,0 +1,105 @@ +package com.perfectworldprogramming.mobile.orm.helper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.perfectworldprogramming.mobile.orm.creator.SQLLiteCreateStatementGenerator; +import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; + +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +/** + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 7:51 PM + */ +public class AndroidSQLLiteOpenHelper extends SQLiteOpenHelper { + + protected Class[] classes; + + public AndroidSQLLiteOpenHelper(Context context, Class[] classes, String dataBaseName, int dataBaseVersion) { + super(context, dataBaseName, null, dataBaseVersion); + this.classes = classes; + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + SQLLiteCreateStatementGenerator sqlLiteCreateStatementGenerator = new SQLLiteCreateStatementGenerator(); + for (Class clazz : classes) { + sqLiteDatabase.execSQL(sqlLiteCreateStatementGenerator.createCreateStatement(clazz)); + } + } + + /* + * beginTransaction + run a table creation with if not exists (we are doing an upgrade, so the table might not exists yet, it will fail alter and drop) + put in a list the existing columns List columns = DBUtils.GetColumns(db, TableName); + backup table (ALTER table " + TableName + " RENAME TO 'temp_" + TableName) + create new table (the newest table creation schema) + get the intersection with the new columns, this time columns taken from the upgraded table (columns.retainAll(DBUtils.GetColumns(db, TableName));) + restore data (String cols = StringUtils.join(columns, ","); db.execSQL(String.format( "INSERT INTO %s (%s) SELECT %s from temp_%s", TableName, cols, cols, TableName)); ) + remove backup table (DROP table 'temp_" + TableName) + setTransactionSuccessful + */ + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + SQLLiteCreateStatementGenerator sqlLiteCreateStatementGenerator = new SQLLiteCreateStatementGenerator(); + sqLiteDatabase.beginTransaction(); + for (Class clazz : classes) { + // Thanks to Pentium10 at Stack Overflow for this solution. + String createStatementIfNotExists = sqlLiteCreateStatementGenerator.createCreateIfNotExistsStatement(clazz); + String tableName = clazz.getSimpleName(); + try { + sqLiteDatabase.execSQL(createStatementIfNotExists); + List columns = getColumns(sqLiteDatabase, tableName); + sqLiteDatabase.execSQL("ALTER table " + tableName + " RENAME TO 'temp_" + tableName + "'"); + sqLiteDatabase.execSQL(sqlLiteCreateStatementGenerator.createCreateStatement(clazz)); + columns.retainAll(getColumns(sqLiteDatabase, tableName)); + String cols = join(columns, ","); + sqLiteDatabase.execSQL(String.format( "INSERT INTO %s (%s) SELECT %s from temp_%s", tableName, cols, cols, tableName)); + sqLiteDatabase.execSQL("DROP table 'temp_" + tableName +"'"); + } catch (SQLException e) { + Log.v(tableName, e.getMessage(), e); + throw new DataAccessException(e.getMessage()); + } + } + sqLiteDatabase.setTransactionSuccessful(); + sqLiteDatabase.endTransaction(); + + } + + private static List getColumns(SQLiteDatabase db, String tableName) { + List ar = null; + Cursor c = null; + try { + c = db.rawQuery("select * from " + tableName + " limit 1", null); + if (c != null) { + ar = new ArrayList(Arrays.asList(c.getColumnNames())); + } + } catch (Exception e) { + Log.v(tableName, e.getMessage(), e); + e.printStackTrace(); + } finally { + if (c != null) + c.close(); + } + return ar; + } + + private static String join(List list, String delim) { + StringBuilder buf = new StringBuilder(); + int num = list.size(); + for (int i = 0; i < num; i++) { + if (i != 0) + buf.append(delim); + buf.append((String) list.get(i)); + } + return buf.toString(); + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/helper/DBHelper.java b/src/com/perfectworldprogramming/mobile/orm/helper/DBHelper.java new file mode 100644 index 0000000..742bd81 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/helper/DBHelper.java @@ -0,0 +1,38 @@ +package com.perfectworldprogramming.mobile.orm.helper; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +/** + * This class might be deprecated if not needed + * + * User: Mark Spritzler + * Date: 3/10/11 + * Time: 8:23 PM + */ +public class DBHelper { + private SQLiteDatabase sqlLiteDatabase; + private final AndroidSQLLiteOpenHelper openHelper; + + public DBHelper(Context context, Class[] classes, String dataBaseName, int dataBaseVersion) { + System.out.println(" " + context + " " + classes + " " + dataBaseName + " " + dataBaseVersion); + openHelper = new AndroidSQLLiteOpenHelper(context, classes, dataBaseName, dataBaseVersion); + establishDb(); + } + + private void establishDb() { + if (sqlLiteDatabase == null) { + sqlLiteDatabase = openHelper.getWritableDatabase(); + } +} + public void cleanup() { + if (sqlLiteDatabase != null) { + sqlLiteDatabase.close(); + sqlLiteDatabase = null; + } + } + + public SQLiteDatabase getSqlLiteDatabase() { + return sqlLiteDatabase; + } +} diff --git a/src/com/perfectworldprogramming/mobile/orm/interfaces/CursorExtractor.java b/src/com/perfectworldprogramming/mobile/orm/interfaces/CursorExtractor.java new file mode 100644 index 0000000..6f824dd --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/interfaces/CursorExtractor.java @@ -0,0 +1,31 @@ +package com.perfectworldprogramming.mobile.orm.interfaces; + +import android.database.Cursor; + +/** + * Callback interface used by {@link com.perfectworldprogramming.mobile.orm.AndroidSQLiteTemplate}'s query methods. + * Implementations of this interface perform the actual work of extracting + * results from a {@link java.sql.ResultSet}, but don't need to worry + * about exception handling. {@link java.sql.SQLException SQLExceptions} + * will be caught and handled by the calling AndroidSQLiteTemplate. + * + *

This interface is mainly used within the SQLLite framework itself. + * A {@link CursorRowMapper} is usually a simpler choice for Cursor processing, + * mapping one result object per row instead of one result object for + * the entire ResultSet. + * + * @author Mark Spritzler + * @since 3/19/11 + * @see com.perfectworldprogramming.mobile.orm.AndroidSQLiteTemplate + * @see CursorRowMapper + */ +public interface CursorExtractor { + + /** + * Implementations must implement this method to process the entire Cursor. + * @param cursor Cursor to extract data from. Implementations should + * not close this: it will be closed by the calling AndroidSQLiteTemplate. + * @return an arbitrary result object, or null if none + */ + T extractData(Cursor cursor); +} diff --git a/src/com/perfectworldprogramming/mobile/orm/interfaces/CursorRowMapper.java b/src/com/perfectworldprogramming/mobile/orm/interfaces/CursorRowMapper.java new file mode 100644 index 0000000..221b0ff --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/interfaces/CursorRowMapper.java @@ -0,0 +1,13 @@ +package com.perfectworldprogramming.mobile.orm.interfaces; + +import android.database.Cursor; + +/** + * User: Mark Spritzler + * Date: 3/19/11 + * Time: 2:35 PM + */ +public interface CursorRowMapper { + + T mapRow(Cursor cursor, int rowNum); +} diff --git a/src/com/perfectworldprogramming/mobile/orm/interfaces/JdbcOperations.java b/src/com/perfectworldprogramming/mobile/orm/interfaces/JdbcOperations.java new file mode 100644 index 0000000..86d37b7 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/interfaces/JdbcOperations.java @@ -0,0 +1,219 @@ +package com.perfectworldprogramming.mobile.orm.interfaces; + +import java.util.List; + +import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; +import com.perfectworldprogramming.mobile.orm.exception.NoRowsReturnedException; + +/** + * Interface for the Android Template class. Since there is only one template + * class this interface isn't as necessary, but created to be as close a match to + * JDBCTemplate in Core Spring. So if you have used JDBCTemplate before, you will + * be completely familiar with the AndroidSQLiteTemplate class with same method + * names and functionality. + * + * User: Mark Spritzler + * Date: 3/19/11 + * Time: 2:31 PM + */ +public interface JdbcOperations { + + /** + * Creates an insert statement and inserts the data in the Domain object passed in + * as a parameter. + * + * The domain object passed in must mapped with the @PrimaryKey, @Column and @ForeignKey + * annotations. If your domain object is not mapped and you want to pass in an insert sql + * statement look at + * @param object domain object without an ID, and not inserted in the database yet + * @return long, number of rows inserted. Should return 1 if successful + */ + public long insert(Object object) throws DataAccessException; + + /** + * Handles insert SQL statement to the database. + * Use this method instead of insert(Object object + * if your domain objects are not mapped with the ORM Annotations. + * + * @param sql Insert sql statement + * @param args values to replace parameters within the insert statement + * @return long, number of rows inserted. Should return 1 if successful + */ + public long insert(String sql, Object... args) throws DataAccessException; + + /** + * Creates an update statement and updates the data in the Domain object passed in + * as a parameter. + * + * The domain object passed in must mapped with the @PrimaryKey, @Column and @ForeignKey + * annotations. If your domain object is not mapped and you want to update the data + * look at update(String sql, Object... args + * + * @param object domain object without an ID, and not inserted in the database yet + * @return long, number of rows updated. Should return 1 if successful + */ + public long update(Object object) throws DataAccessException; + + /** + * Handles updates to the database. + * + * + * Use this method instead of update(Object object) + * if your domain objects are not mapped with the ORM Annotations. + * + * @param sql update statement SQL + * @param args values to set the parameters in the update statement + */ + public void update(String sql, Object... args) throws DataAccessException; + + /** + * + * @param object + * @return + */ + public long delete(Object object) throws DataAccessException; + + /** + * Delete a single row in a table based on a single value for a single field. + * + * While this will call delete(table, whereClause, whereArgs) on the underlying SQLiteDatabase object, it will not allow an array of ids + * or a where clause. If you want that functionality use {@link #delete(String, Object...)} template method instead. + * + * @param table table to delete from + * @param columnName field to use in the where clause of the delete statement + * @param columnValue primary key value + * @return long should return 1 if successful + */ + public long delete(String table, String columnName, String columnValue) throws DataAccessException; + + /** + * + * @param sql + * @param args + */ + public void delete(String sql, Object... args) throws DataAccessException; + + /** + * Runs a query where one row with one field is expected in results, where + * the field value is an int. + * + * However, if the type of that field is not an int, then 0 will always be returned. + * This is because of the underlying Android SQLite implementation of running a simple + * query for int. + * + * If the result returns more than one row, the first row's data will be returned. + * If the result returned has zero rows, then a DataAccessException is thrown. + * + * @param sql select statement sql with one field in the select portion + * @param args values to set the parameters in the update statement + * @return int the value of the field. 0 if the field type is not a number + * @throws DataAccessException when the result set returned zero rows + */ + public int queryForInt(String sql, Object... args) throws DataAccessException; + + /** + * Runs a query where one row with one field is expected in results, where + * the field value is a long. + * + * However, if the type of that field is not an long, then 0 will always be returned. + * This is because of the underlying Android SQLite implementation of running a simple + * query for long. + * + * If the result returns more than one row, the first row's data will be returned. + * If the result set returned has zero rows, then a NoRowsReturnedException is thrown. + * + * @param sql select statement sql with one field in the select portion + * @param args values to set the parameters in the update statement + * @return long the value of the field. 0 if the field type is not a number + * @throws NoRowsReturnedException when the result set returned zero rows + */ + public long queryForLong(String sql, Object... args) throws DataAccessException; + + /** + * Runs a query where one row with one field is expected in results, where + * the field value is a String. + * + * However, if the type of that field is not an long, then SQLite will convert it to a String. + * This is because of the underlying Android SQLite implementation of running a simple + * query for String. + * + * If the result returns more than one row, the first row's data will be returned. + * If the result set returned has zero rows, then a NoRowsReturnedException is thrown. + * + * @param sql select statement sql with one field in the select portion + * @param args values to set the parameters in the update statement + * @return String the value of the field. if the field type is not a string the type is converted into a String + * @throws NoRowsReturnedException when the result set returned zero rows + */ + public String queryForString(String sql, Object... args) throws DataAccessException, NoRowsReturnedException; + + /** + * + * Runs a query where one row expected in results + * + * However, if the type of that field is not an long, then SQLite will convert it to a String. + * This is because of the underlying Android SQLite implementation of running a simple + * query for String. + * + * If the result returns more than one row, the first row's data will be returned. + * If the result set returned has zero rows, then a NoRowsReturnedException is thrown. + * + * @param sql select statement sql with one field in the select portion + * @param clazz Class of the domain object to create with the results from the Cursor + * @param args values to set the parameters in the update statement + * @param Type of the Domain object that is returned + * @return the domain object filled with the values from the first row of the Cursor + * @throws NoRowsReturnedException when the result set returned zero rows + */ + public T queryForObject(String sql, Class clazz, Object... args) throws NoRowsReturnedException; + + /** + * + * @param sql + * @param cursorRowMapper + * @param args + * @param + * @return + */ + public T queryForObject(String sql, CursorRowMapper cursorRowMapper, Object... args) throws DataAccessException; + + /** + * + * @param sql + * @param cursorExtractor + * @param args + * @param + * @return + */ + public T queryForObject(String sql, CursorExtractor cursorExtractor, Object... args) throws DataAccessException; + + /** + * + * @param clazz + * @param id + * @param + * @return + */ + public T findById(Class clazz, Long id) throws DataAccessException; + + /** + * + * @param sql + * @param clazz + * @param args + * @param + * @return + */ + public List query(String sql, Class clazz, Object... args) throws DataAccessException; + + /** + * + * @param sql + * @param cursorRowMapper + * @param args + * @param + * @return + */ + public List query(String sql, CursorRowMapper cursorRowMapper, Object... args) throws DataAccessException; + +} diff --git a/src/com/perfectworldprogramming/mobile/orm/reflection/DomainClassAnalyzer.java b/src/com/perfectworldprogramming/mobile/orm/reflection/DomainClassAnalyzer.java new file mode 100644 index 0000000..aad7f89 --- /dev/null +++ b/src/com/perfectworldprogramming/mobile/orm/reflection/DomainClassAnalyzer.java @@ -0,0 +1,130 @@ +package com.perfectworldprogramming.mobile.orm.reflection; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ForeignKey; +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; +import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; +import com.perfectworldprogramming.mobile.orm.exception.NoPrimaryKeyFieldException; + +import android.content.ContentValues; + +/** + * User: Mark Spritzler + * Date: 3/12/11 + * Time: 9:48 PM + */ +public class DomainClassAnalyzer { + + public Field getPrimaryKeyField(Class clazz) { + Field primaryKeyField = null; + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class); + if (primaryKey != null) { + primaryKeyField = field; + break; + } + } + if (primaryKeyField == null) { + throw new NoPrimaryKeyFieldException(clazz); + } + return primaryKeyField; + } + + public String getPrimaryKeyFieldName(Class clazz) { + PrimaryKey primaryKey = getPrimaryKey(clazz); + return primaryKey.value(); + } + + public PrimaryKey getPrimaryKey(Class clazz) { + return getPrimaryKeyField(clazz).getAnnotation(PrimaryKey.class); + } + + public Field[] getForeignKeyFields(Class clazz) { + List foreignKeyFields = new ArrayList(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + ForeignKey foreignKey = field.getAnnotation(ForeignKey.class); + if (foreignKey != null) { + foreignKeyFields.add(field); + break; + } + } + return foreignKeyFields.toArray(new Field[foreignKeyFields.size()]); + } + + public long getIdFromObject(Object o) { + Long id = -1l; + Class clazz = o.getClass(); + Field primaryKeyField = getPrimaryKeyField(clazz); + primaryKeyField.setAccessible(true); + try { + id = (Long)primaryKeyField.get(o); + if (id == null) { + throw new DataAccessException("Domain classes must have an @PrimaryKey property in order to use the ORM functionality. Your " + o.getClass().getName() + " class is missing @PrimaryKey"); + } + } catch (IllegalAccessException e) { + throw new DataAccessException(e.getMessage()); + } + return id; + } + + public void setIdToNewObject(Object object, long id) { + Field fieldToSet = this.getPrimaryKeyField(object.getClass()); + fieldToSet.setAccessible(true); + try { + fieldToSet.set(object, id); + } catch (IllegalAccessException e) { + throw new DataAccessException(e.getMessage()); + } + } + + public ContentValues createContentValues(Object object) { + Class clazz = object.getClass(); + Field[] fields = clazz.getDeclaredFields(); + ContentValues values = new ContentValues(fields.length); + for (Field field : fields) { + Column column = field.getAnnotation(Column.class); + if (column != null) { + addColumnValuesToContentValues(field, values, column.value(), object); + } else { + ForeignKey foreignKey = field.getAnnotation(ForeignKey.class); + if (foreignKey != null) { + addForeignKeyValuesToContentValues(field, foreignKey, values, object); + } + } + } + return values; + } + + private void addColumnValuesToContentValues(Field field, ContentValues values, String fieldName, Object object) { + field.setAccessible(true); + try { + Object value = field.get(object); + if (value != null) { + values.put(fieldName, value.toString()); + } + } catch (IllegalAccessException e) { + throw new DataAccessException("Unable to get the Column value from the domain object: " + object.getClass().getName() + " for Field: " + fieldName); + } + } + + private void addForeignKeyValuesToContentValues(Field field, ForeignKey foreignKey, ContentValues values, Object domainObject) { + field.setAccessible(true); + try { + Object foreignDomainObject = field.get(domainObject); + if (foreignDomainObject != null) { + Object value = getIdFromObject(foreignDomainObject); + if (value != null) { + values.put(foreignKey.value(), value.toString()); + } + } + } catch (IllegalAccessException e) { + throw new DataAccessException("Unable to get the Foreign Key value from the domain object: " + domainObject.getClass().getName()); + } + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/AndroidSQLiteTemplateTests.java b/test/com/perfectworldprogramming/mobile/orm/test/AndroidSQLiteTemplateTests.java new file mode 100644 index 0000000..a8f99ee --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/AndroidSQLiteTemplateTests.java @@ -0,0 +1,886 @@ +package com.perfectworldprogramming.mobile.orm.test; + +import java.util.List; + + +import com.perfectworldprogramming.mobile.orm.AndroidSQLiteTemplate; +import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; +import com.perfectworldprogramming.mobile.orm.exception.EmptySQLStatementException; +import com.perfectworldprogramming.mobile.orm.exception.InvalidCursorExtractorException; +import com.perfectworldprogramming.mobile.orm.exception.InvalidCursorRowMapperException; +import com.perfectworldprogramming.mobile.orm.helper.DBHelper; +import com.perfectworldprogramming.mobile.orm.interfaces.CursorExtractor; +import com.perfectworldprogramming.mobile.orm.interfaces.CursorRowMapper; +import com.perfectworldprogramming.mobile.orm.reflection.DomainClassAnalyzer; +import com.perfectworldprogramming.mobile.orm.test.domain.Account; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.NoPKDomain; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; +import com.perfectworldprogramming.mobile.orm.test.interfaces.AddressCursorExtractor; +import com.perfectworldprogramming.mobile.orm.test.interfaces.AddressCursorRowMapper; +import com.perfectworldprogramming.mobile.orm.test.interfaces.PersonCursorExtractor; +import com.perfectworldprogramming.mobile.orm.test.interfaces.PersonCursorRowMapper; +import com.perfectworldprogramming.mobile.orm.test.interfaces.SampleDataHelper; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.test.ActivityInstrumentationTestCase2; +import android.util.Log; + +/** + * User: Mark Spritzler + * Date: 4/7/11 + * Time: 12:06 PM + */ +public class AndroidSQLiteTemplateTests extends ActivityInstrumentationTestCase2

{ + + public AndroidSQLiteTemplateTests() { + super("org.springframework.mobile.orm.test", Main.class); + } + + AndroidSQLiteTemplate template; + List sampleAccounts; + + SQLiteDatabase dataBase; + + @SuppressWarnings("unchecked") + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + DBHelper helper = new DBHelper(this.getInstrumentation().getContext(), new Class[]{Person.class, Address.class, Account.class}, "ormtest", 3); + template = new AndroidSQLiteTemplate(helper.getSqlLiteDatabase()); + SampleDataHelper.addDataToDatabase(template); + sampleAccounts = SampleDataHelper.getAccounts(); + dataBase =helper.getSqlLiteDatabase(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + + + // Currently passes + public void testSuccessInsertWithDomainObject() { + Person person = new Person(); + person.setAge(5); + person.setFirstName("Ryan"); + person.setLastName("Simpson"); + person.setHeight(2.6); + long id = template.insert(person); + + // Check out by using the api directly. + Cursor cursor = dataBase.rawQuery("Select * from Person where PERSON_ID = " + id, null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("Ryan", cursor.getString(cursor.getColumnIndex("FIRST_NAME"))); + assertEquals("Simpson", cursor.getString(cursor.getColumnIndex("LAST_NAME"))); + } + + //Currently passes but needs more code + /* + * Missing Mandatory/not Nullable Field + * Field that is set to unique is not unique (next version, but the code still should work in this scenario and would throw a DataAccessException) + */ + public void testFailureInsertWithDomainObject() { + //missing mandatory field + Person person = new Person(); + person.setAge(5); + person.setLastName("Simpson"); + person.setHeight(2.6); + try { + template.insert(person); + fail("Exception should be thrown because a mandatory field is not set"); + } catch (DataAccessException dae) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite"); + } + } + + // Currently Passes + public void testSuccessInsertSQL() { + String sql = "INSERT INTO Person (FIRST_NAME, LAST_NAME, AGE) VALUES ('?', '?', ?)"; + Object[] args = {"Bugs", "Bunny", 10}; + long id = template.insert(sql, args); + + Cursor cursor = dataBase.rawQuery("Select * from Person where PERSON_ID = " + id, null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals("Bugs", cursor.getString(cursor.getColumnIndex("FIRST_NAME"))); + assertEquals("Bunny", cursor.getString(cursor.getColumnIndex("LAST_NAME"))); + assertEquals(10, cursor.getInt(cursor.getColumnIndex("AGE"))); + } + + // Currently Passes + /* + * Invalid Insert statement string using FIELDS + * Using an update statement instead of an insert + */ + public void testFailureInsertSQL() { + String sql = "INSERT INTO Person FIELDS (FIRST_NAME, LAST_NAME, AGE) VALUES ('?', '?', ?)"; + Object[] args = {"Bugs", "Bunny", 10}; + try { + template.insert(sql, args); + fail("Exception should be thrown because a insert statement is invalid"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite"); + } + + sql = "UPDATE Person (FIRST_NAME, LAST_NAME, AGE) VALUES ('?', '?', ?)"; + try { + template.insert(sql, args); + fail("Exception should be thrown because it is an update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite"); + } + } + + // Currently passes + public void testSuccessUpdateWithDomainObject() { + // Take the last account in collection and make changes + // First just a Real field + Account account = sampleAccounts.get(sampleAccounts.size()-1); + account.setAmount(42.00); + long numberOfAccountsUpdated = template.update(account); + + assertEquals(1, numberOfAccountsUpdated); + assertAccount(account, "Select * from Account where YEAR_STARTED=2011"); + + // Now an Integer field + account.setYearAccountOpened(1900); + numberOfAccountsUpdated = template.update(account); + assertEquals(1, numberOfAccountsUpdated); + assertAccount(account, "Select * from Account where YEAR_STARTED=1900"); + + // Now a Text field + account.setAccountType("Invalid"); + numberOfAccountsUpdated = template.update(account); + assertEquals(1, numberOfAccountsUpdated); + assertAccount(account, "Select * from Account where YEAR_STARTED=1900"); + + // Now all three field types in one update + account.setAccountType("Business Pro"); + account.setYearAccountOpened(2011); + account.setAmount(4200.00); + numberOfAccountsUpdated = template.update(account); + assertEquals(1, numberOfAccountsUpdated); + assertAccount(account, "Select * from Account where YEAR_STARTED=2011"); + + } + + + // Currently Passes + public void testFailureUpdateWithDomainObject() { + //No Id set in domain object + Account account = new Account(); + try { + template.update(account); + fail("Exception should be thrown because id is not set"); + } catch (DataAccessException de) { + } catch (Exception e) { + Log.i("Template Test", Log.getStackTraceString(e)); + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // Use an object that is not mapped + Long longObject = new Long(5); + try { + template.update(longObject); + fail("Exception should be thrown because Long is not a domain object"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // Object is mapped, but not a table in the database + NoPKDomain noPK = new NoPKDomain(); + try { + template.update(noPK); + fail("Exception should be thrown because there isn't a table for this domain mapped object"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + } + + // Currently passes + public void testSuccessUpdateSQL() { + // Update statement to one row + String sql = "UPDATE Account set AMOUNT = ? where ACCOUNT_TYPE = '?'"; + Object[] args = {new Double(68.00), "Personal"}; + template.update(sql, args); + + Cursor cursor = dataBase.rawQuery("Select Amount from Account where ACCOUNT_TYPE='Personal'", null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + Double results = cursor.getDouble(0); + assertEquals(new Double(68.00), results); + + // Update statement to more than one row + sql = "UPDATE Account set AMOUNT = ? where ACCOUNT_TYPE = '?'"; + args[0] = new Double(67.00); + args[1] = "Business"; + template.update(sql, args); + cursor = dataBase.rawQuery("Select Amount from Account where ACCOUNT_TYPE='Business'", null); + assertNotNull(cursor); + assertEquals(3, cursor.getCount()); + cursor.moveToFirst(); + results = cursor.getDouble(0); + assertEquals(new Double(67.00), results); + + // a delete statement + // This will work, because you can pass a delete statement to update + template.update("Delete from Account where ACCOUNT_ID = ?", new Object[]{1}); + + } + + + /* Currently passes + * Null sql, + * invalid update + * table doesn't exist + * set field doesn't exist + * set field to wrong type + * an insert statement, + * a select statement + * + */ + public void testFailureUpdateSQL() { + String sql = ""; + Object[] nullArgs = {}; + + // empty sql + try { + template.update(sql, nullArgs); + fail("Exception should be thrown because there isn't an update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // table doesn't exist + sql = "UPDATE NonExistingTable set AMOUNT = ? where ACCOUNT_TYPE = '?'"; + Object[] validArgs = {new Double(67.00), "Business"}; + try { + template.update(sql, validArgs); + fail("Exception should be thrown because the table doesn't exist in update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // set field doesn't exist + sql = "UPDATE Account set Non_Field = ? where ACCOUNT_TYPE = '?'"; + try { + template.update(sql, validArgs); + fail("Exception should be thrown because the field doesn't exist update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // set field to wrong type + sql = "UPDATE Account set AMOUNT = ? where ACCOUNT_TYPE = '?'"; + Object[] invalidArgs = {"Hello", "Business"}; + try { + template.update(sql, invalidArgs); + fail("Exception should be thrown because the field value is the wrong type update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // an insert statement, + sql = "INSERT INTO Person FIELDS (FIRST_NAME, LAST_NAME, AGE) VALUES ('?', '?', ?)"; + Object[] insertArgs = {"Bugs", "Bunny", 10}; + try { + template.update(sql, insertArgs); + fail("Exception should be thrown because this is an insert statement not an update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // a select statement + sql = "Select * from Person"; + try { + template.update(sql, new Object[]{}); + fail("Exception should be thrown because this is a select statement not an update statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + } + + // Currently passes + public void testSuccessDeleteWithDomainObject() { + Account account = sampleAccounts.get(5); + long results = template.delete(account); + assertEquals(1, results); + } + + /* + * Currently passes + * null domain object + * domain object with invalid id + * domain object without an id + */ + public void testFailureDeleteWithDomainObject() { + Account account = null; + try { + template.delete(account); + fail("Exception should be thrown because the domain object is null"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + account = new Account(); + DomainClassAnalyzer analyzer = new DomainClassAnalyzer(); + analyzer.setIdToNewObject(account, 42); + try { + template.delete(account); + fail("Exception should be thrown because this is an invalid id"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + account = new Account(); + try { + template.delete(account); + fail("Exception should be thrown because the id in the domain object is null"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + } + + // Currently Passes + public void testSuccessDeleteWithTableNameAndIdsToDelete() { + String table = "Account"; + String keyColumnName = "ACCOUNT_ID"; + String keyValue = "1"; + long results = template.delete(table, keyColumnName, keyValue); + assertEquals(1, results); + + keyValue = "2"; + results = template.delete(table, keyColumnName, keyValue); + assertEquals(1, results); + } + + /* + * Currently Passes + * empty strings and string array + * null table + * null keyColumnName + * null keyValues + * Wrong table name + * Wrong key column name + * Wrong id doesn't exist + */ + public void testFailureDeleteWithTableNameAndIdsToDelete() { + // empty strings and string array + String table = ""; + String keyColumnName = ""; + String keyValue = ""; + try { + template.delete(table, keyColumnName, keyValue); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + //null table + table = null; + keyColumnName = "ACCOUNT_ID"; + keyValue = "1"; + try { + template.delete(table, keyColumnName, keyValue); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // null keyColumnName + table = "Account"; + keyColumnName = null; + try { + template.delete(table, keyColumnName, keyValue); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // null keyValues + keyColumnName = "ACCOUNT_ID"; + keyValue = null; + try { + template.delete(table, keyColumnName, keyValue); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // Fix the null from previous test + keyValue = "1"; + + // Wrong table name + table = "NonExistingTable"; + try { + template.delete(table, keyColumnName, keyValue); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // Wrong key column name + table = "Account"; + keyColumnName = "BAD_ID"; + try { + template.delete(table, keyColumnName, keyValue); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // Wrong id doesn't exist + keyColumnName = "ACCOUNT_ID"; + keyValue = "42"; + long results = template.delete(table, keyColumnName, keyValue); + assertEquals(0, results); + } + + // Currently Passes + public void testSuccessDeleteWithSQL() { + String sql = "Delete from Account where ACCOUNT_TYPE = '?'"; + Object[] args = {"Personal"}; + template.delete(sql, args); + + sql = "Delete from Account where ACCOUNT_TYPE = '?'"; + args[0] = "Business"; + template.delete(sql, args); + } + + // Currently Passes + public void testFailureDeleteWithSQL() { + String sql = ""; + Object[] nullArgs = {}; + + // empty sql + try { + template.delete(sql, nullArgs); + fail("Exception should be thrown because there isn't a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // table doesn't exist + sql = "Delete from NonExistingTable where ACCOUNT_TYPE = '?'"; + Object[] validArgs = {"Business"}; + try { + template.delete(sql, validArgs); + fail("Exception should be thrown because the table doesn't exist in delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // an insert statement, + sql = "INSERT INTO Person FIELDS (FIRST_NAME, LAST_NAME, AGE) VALUES ('?', '?', ?)"; + Object[] insertArgs = {"Bugs", "Bunny", 10}; + try { + template.delete(sql, insertArgs); + fail("Exception should be thrown because this is an insert statement not a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // a select statement + sql = "Select * from Person"; + try { + template.delete(sql, new Object[]{}); + fail("Exception should be thrown because this is a select statement not a delete statement"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + } + + + // Currently passes + public void testSuccessQueryForInt() { + String sql = "Select count(*) from Person"; + String args = ""; + Integer results = template.queryForInt(sql, args); + + assertNotNull(results); + Cursor cursor = dataBase.rawQuery("Select count(*) from Person", null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + Integer realCount = cursor.getInt(0); + assertEquals(realCount, results); + } + + // Currently Passes + public void testFailureQueryForInt() { + String sql = "Select FIRST_NAME from Person Where FIRST_NAME='?'"; + String args = "George"; + try { + template.queryForInt(sql, args); + fail("DataAccessException should be thrown because query returns zero rows"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + // returns one row, but since it is a String the int is set to 0; + args = "John"; + int zero = template.queryForInt(sql, args); + assertEquals(0, zero); + + } + + // Currently passes + public void testSuccessQueryForLong() { + String sql = "Select count(*) from Person"; + String args = ""; + Long results = template.queryForLong(sql, args); + + assertNotNull(results); + Cursor cursor = dataBase.rawQuery("Select count(*) from Person", null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + Long realCount = cursor.getLong(0); + assertEquals(realCount, results); + } + + // Currently Passes + public void testFailureQueryForLong() { + String sql = "Select FIRST_NAME from Person Where FIRST_NAME='?'"; + String args = "George"; + try { + template.queryForLong(sql, args); + fail("DataAccessException should be thrown because query returns zero rows"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + args = "John"; + long zero = template.queryForLong(sql, args); + assertEquals(0, zero); + + } + + // Currently passes + public void testSuccessQueryForString() { + String sql = "Select FIRST_NAME from Person Where FIRST_NAME='?'"; + String args = "John"; + String results = template.queryForString(sql, args); + + assertNotNull(results); + assertEquals("John", results); + + // Still will be successful and convert the int field to a String + sql = "Select AGE from Person Where FIRST_NAME='?'"; + results = template.queryForString(sql, args); + assertNotNull(results); + assertEquals("42", results); + + // Still will be successful with more than one row but return just the first row's value + sql = "Select age from Person"; + args = null; + results = template.queryForString(sql, args); + assertNotNull(results); + assertEquals("42", results); + + } + + // Currently passes + public void testFailureQueryForString() { + String sql = "Select AGE from Person Where FIRST_NAME='?'"; + String args = "George"; + try { + template.queryForString(sql, args); + fail("DataAccessException should be thrown because it is returning 0 rows"); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + } + + // Currently passes + public void testSuccessQueryForObjectWithDomainClass() { + String sql="Select * from Person where FIRST_NAME='?'"; + Class clazz = Person.class; + String args="John"; + Person person = template.queryForObject(sql, clazz, args); + assertJohnThePerson(person); + + // Still successful because SQLite returns the first row even if the query returns more than one row + sql="Select * from Person"; + args = null; + person = template.queryForObject(sql, clazz, args); + assertJohnThePerson(person); + } + + // Currently passes. + public void testFailureQueryForObjectWithDomainClass() { + String sql="Select * from Person where FIRST_NAME='?'"; + Class clazz = Person.class; + String args="George"; + try { + Object results = template.queryForObject(sql, clazz, args); + fail("DataAccessException should be thrown because results returned 0 rows but returns: " + results); + } catch (DataAccessException de) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + } + + // Currently passes + public void testSuccessQueryForObjectWithCursorRowMapper() { + String sql = "Select * from ADDRESS where ZIP_CODE='?'"; + AddressCursorRowMapper addressCursorRowMapper = new AddressCursorRowMapper(); + Address address = template.queryForObject(sql, addressCursorRowMapper, "12345"); + + assertNotNull(address); + assertEquals("Philadelphia", address.getCity()); + assertEquals("PA", address.getState()); + assertEquals("123 Broad Street", address.getStreet()); + assertEquals("12345", address.getZipCode()); + } + + // Currently passes + public void testFailuresQueryForObjectWithCursorRowMapper() { + String sql=""; + CursorRowMapper
cursorRowMapper = null; + String args=""; + try { + template.queryForObject(sql, cursorRowMapper, args); + fail("Should throw an IllegalArgumentException because the cursorRowMapper is null"); + } catch (IllegalArgumentException ie) { + } catch (Exception e) { + fail("Only an IllegalArgumentException should be thrown"); + } + + cursorRowMapper = new AddressCursorRowMapper(); + try { + template.queryForObject(sql, cursorRowMapper, args); + fail("Should throw an EmptySQLStatementException because the cursorRowMapper is null"); + } catch (EmptySQLStatementException ie) { + } catch (Exception e) { + fail("Only an EmptySQLStatementException should be thrown " + e.getMessage()); + } + + sql = "Select * from Person Where FIRST_NAME = '?'"; + args = "John"; + try { + template.queryForObject(sql, cursorRowMapper, args); + fail("Should throw an InvalidCursorRowMapperException because the cursorRowMapper is of the wrong type. it is an Address Row mapper but the query is from Person"); + } catch (InvalidCursorRowMapperException ie) { + } catch (Exception e) { + fail("Only an EmptySQLStatementException should be thrown: " + e.getMessage()); + } + } + + // Currently passes + public void testSuccessQueryForObjectWithCursorExtractor() { + String sql = "SELECT * from PERSON p, ADDRESS a where a.PERSON_ID = p.PERSON_ID and p.FIRST_NAME = '?'"; + Person person = template.queryForObject(sql, new PersonCursorExtractor(), "John"); + + assertNotNull(person); + assertEquals(new Integer(42), person.getAge()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(new Double("5.1d"), person.getHeight()); + List
addresses = person.getAddresses(); + assertNotNull(addresses); + assertEquals(2, addresses.size()); + } + + // Currently passes + public void testFailureQueryForObjectWithCursorExtractor() { + String sql=""; + CursorExtractor
cursorExtractor = null; + String args=""; + try { + template.queryForObject(sql, cursorExtractor, args); + fail("Should throw an IllegalArgumentException because the cursorExtractor is null"); + } catch (IllegalArgumentException ie) { + } catch (Exception e) { + fail("Only an IllegalArgumentException should be thrown"); + } + + cursorExtractor = new AddressCursorExtractor(); + try { + template.queryForObject(sql, cursorExtractor, args); + fail("Should throw an EmptySQLStatementException because the cursorExtractor is null"); + } catch (EmptySQLStatementException ie) { + } catch (Exception e) { + fail("Only an EmptySQLStatementException should be thrown " + e.getMessage()); + } + + sql = "Select * from Person"; + try { + template.queryForObject(sql, cursorExtractor, args); + fail("Should throw an InvalidCursorExtractorException because the query does not return enough fields or from the Address table in a join clause"); + } catch (InvalidCursorExtractorException ie) { + } catch (Exception e) { + fail(e.getMessage()); + } + + } + + // Currently passes + public void testSuccessFindById() { + Person person = template.findById(Person.class, 1l); + assertJohnThePerson(person); + } + + // Currently passes + public void testFailedFindById() { + Person person = template.findById(Person.class, 50l); + assertNull(person); + } + + // Currently passes + public void testSuccessQueryWithDomainClass() { + List people = template.query("Select * from Person Where FIRST_NAME='?'", Person.class, "John"); + + assertNotNull(people); + assertEquals(1, people.size()); + Person person = people.get(0); + assertJohnThePerson(person); + } + + // Currently passes but needs more code, null sql, empty sql + public void testFailureQueryWithDomainClass() { + List people = template.query("Select * from Person Where FIRST_NAME='?'", Person.class, "Jonathon"); + assertNotNull(people); + assertEquals(0, people.size()); + + // Null sql + String sql = null; + try { + template.query(sql, Person.class, "Jonathon"); + fail("Should throw an EmptySQLStatementException because the query does not return enough fields or from the Address table in a join clause"); + } catch (EmptySQLStatementException esse) { + } catch (Exception e) { + fail(e.getMessage()); + } + + // Empty String sql + sql = ""; + try { + template.query(sql, Person.class, "Jonathon"); + fail("Should throw an EmptySQLStatementException because the query does not return enough fields or from the Address table in a join clause"); + } catch (EmptySQLStatementException esse) { + } catch (Exception e) { + fail(e.getMessage()); + } + + // Empty args + sql = "Select * from Person Where FIRST_NAME='?'"; + try { + template.query(sql, Person.class, (Object[])null); + fail("A DataAccessException should be thrown because the arguments for the where clause is null"); + } catch (DataAccessException dae) { + } catch (Exception e) { + fail(e.getMessage()); + } + } + + // Currently passes + public void testSuccessQueryWithCursorRowMapper() { + String sqlStart = "Select * from Person Where"; + CursorRowMapper personCursorRowMapper = new PersonCursorRowMapper(); + String sql = sqlStart + " FIRST_NAME='?'"; + List people = template.query(sql, personCursorRowMapper, "John"); + + assertNotNull(people); + assertEquals(1, people.size()); + Person person = people.get(0); + assertJohnThePerson(person); + + List everyone = template.query("Select * from Person", personCursorRowMapper); + assertNotNull(everyone); + Cursor cursor = dataBase.rawQuery("Select count(*) from Person", null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + int realCount = cursor.getInt(0); + assertEquals(realCount, everyone.size()); + } + + // Currently passes add null sql or null rowmapper tests + public void testFailuresQueryWithCursorRowMapper() { + String sqlStart = "Select * from Person Where"; + CursorRowMapper personCursorRowMapper = new PersonCursorRowMapper(); + String sql = sqlStart + " FIRST_NAME=?"; + try { + template.query(sql, personCursorRowMapper, "John"); + fail("A DataAccessException should be thrown because the String parameter is not quoted"); + } catch (DataAccessException dae) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + + try { + template.query("", personCursorRowMapper); + fail("A DataAccessException should be thrown because there is no query string"); + } catch (DataAccessException dae) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + + try { + template.query("Select * from Address", personCursorRowMapper); + fail("A DataAccessException should be thrown because they are Addresses not People"); + } catch (DataAccessException dae) { + } catch (Exception e) { + fail("This should throw a Spring DataAccessException not one from SQLite " + e.getMessage()); + } + } + + private void assertJohnThePerson(Person john) { + assertEquals(new Integer(42), john.getAge()); + assertEquals("John", john.getFirstName()); + assertEquals("Doe", john.getLastName()); + assertEquals(new Double("5.1d"), john.getHeight()); + } + + private void assertAccount(Account account, String sql) { + Cursor cursor = dataBase.rawQuery(sql, null); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + cursor.moveToFirst(); + Account checkAccount = new Account(); + checkAccount.setAccountType(cursor.getString(cursor.getColumnIndex("ACCOUNT_TYPE"))); + checkAccount.setAmount(cursor.getDouble(cursor.getColumnIndex("AMOUNT"))); + checkAccount.setYearAccountOpened(cursor.getInt(cursor.getColumnIndex("YEAR_STARTED"))); + assertEquals(account, checkAccount); + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/Main.java b/test/com/perfectworldprogramming/mobile/orm/test/Main.java new file mode 100644 index 0000000..e4fdaee --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/Main.java @@ -0,0 +1,17 @@ +package com.perfectworldprogramming.mobile.orm.test; + +import com.perfectworldprogramming.mobile.orm.R; + +import android.app.Activity; +import android.os.Bundle; + +public class Main extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + //AndroidSQLLiteOpenHelper dbHelper = new AndroidSQLLiteOpenHelper(this.getApplicationContext(), new Class[]{Person.class, Address.class}, "ormtest", 1); + //dbHelper.getWritableDatabase(); + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/MyInstrumentationTestRunner.java b/test/com/perfectworldprogramming/mobile/orm/test/MyInstrumentationTestRunner.java new file mode 100644 index 0000000..5e45cca --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/MyInstrumentationTestRunner.java @@ -0,0 +1,38 @@ +package com.perfectworldprogramming.mobile.orm.test; + +import junit.framework.TestSuite; + +import com.perfectworldprogramming.mobile.orm.test.creator.TableCreatorTest; +import com.perfectworldprogramming.mobile.orm.test.helper.DBHelperTests; +import com.perfectworldprogramming.mobile.orm.test.interfaces.CursorExtractorTests; +import com.perfectworldprogramming.mobile.orm.test.interfaces.CursorRowMapperTests; +import com.perfectworldprogramming.mobile.orm.test.reflection.DomainClassAnalyzerTests; + +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; + +/** + * User: Mark Spritzler + * Date: 3/30/11 + * Time: 5:03 PM + */ +public class MyInstrumentationTestRunner extends InstrumentationTestRunner { + @Override + public TestSuite getAllTests() { + InstrumentationTestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(DomainClassAnalyzerTests.class); + suite.addTestSuite(DBHelperTests.class); + //suite.addTestSuite(AndroidSQLiteTemplateTests.class); + suite.addTestSuite(TableCreatorTest.class); + suite.addTestSuite(CursorExtractorTests.class); + suite.addTestSuite(CursorRowMapperTests.class); + + return suite; + } + + @Override + public ClassLoader getLoader() { + return MyInstrumentationTestRunner.class.getClassLoader(); + } + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/creator/TableCreatorTest.java b/test/com/perfectworldprogramming/mobile/orm/test/creator/TableCreatorTest.java new file mode 100644 index 0000000..2921cd5 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/creator/TableCreatorTest.java @@ -0,0 +1,50 @@ +package com.perfectworldprogramming.mobile.orm.test.creator; + +import java.util.List; + + +import com.perfectworldprogramming.mobile.orm.creator.SQLLiteCreateStatementGenerator; +import com.perfectworldprogramming.mobile.orm.test.Main; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * User: Mark Spritzler + * Date: 3/14/11 + * Time: 2:40 PM + */ +public class TableCreatorTest extends ActivityInstrumentationTestCase2
{ + + // TODO need more tests + private SQLLiteCreateStatementGenerator SQLLiteCreateStatementGenerator = new SQLLiteCreateStatementGenerator(); + + public TableCreatorTest() { + super("org.springframework.mobile.orm.test", Main.class); + } + + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + public void testGenerateTables() { + String statement = SQLLiteCreateStatementGenerator.createCreateStatement(Person.class); + assertNotNull("Statement should not be null", statement); + System.out.println(statement); + + SQLLiteCreateStatementGenerator.setClasses(new Class[]{Person.class, Address.class}); + List createStatements = SQLLiteCreateStatementGenerator.getCreateStatements(); + assertNotNull("Should return a list", createStatements); + assertEquals("Should return two statements", 2, createStatements.size()); + for (String aStatement : createStatements) { + System.out.println(aStatement); + } + } + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/domain/Account.java b/test/com/perfectworldprogramming/mobile/orm/test/domain/Account.java new file mode 100644 index 0000000..1e03d79 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/domain/Account.java @@ -0,0 +1,91 @@ +package com.perfectworldprogramming.mobile.orm.test.domain; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ColumnType; +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; + +public class Account { + + @PrimaryKey("ACCOUNT_ID") + private Long id; + + @Column(value="ACCOUNT_TYPE", nullable=false, type=ColumnType.TEXT) + private String accountType; + + @Column(value="AMOUNT", type=ColumnType.REAL) + private double amount; + + @Column(value="YEAR_STARTED", type=ColumnType.INTEGER) + private int yearAccountOpened; + + public Long getId() { + return id; + } + + public String getAccountType() { + return accountType; + } + + public void setAccountType(String accountType) { + this.accountType = accountType; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public int getYearAccountOpened() { + return yearAccountOpened; + } + + public void setYearAccountOpened(int yearStarted) { + this.yearAccountOpened = yearStarted; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((accountType == null) ? 0 : accountType.hashCode()); + long temp; + temp = Double.doubleToLongBits(amount); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + yearAccountOpened; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Account other = (Account) obj; + if (accountType == null) { + if (other.accountType != null) + return false; + } else if (!accountType.equals(other.accountType)) + return false; + if (Double.doubleToLongBits(amount) != Double + .doubleToLongBits(other.amount)) + return false; + if (yearAccountOpened != other.yearAccountOpened) + return false; + return true; + } + + @Override + public String toString() { + return "Account [id=" + id + ", accountType=" + accountType + + ", amount=" + amount + ", yearStarted=" + yearAccountOpened + "]"; + } + + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/domain/Address.java b/test/com/perfectworldprogramming/mobile/orm/test/domain/Address.java new file mode 100644 index 0000000..2de31f1 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/domain/Address.java @@ -0,0 +1,124 @@ +package com.perfectworldprogramming.mobile.orm.test.domain; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ColumnType; +import com.perfectworldprogramming.mobile.orm.annotations.ForeignKey; +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; + +/** + * User: Mark Spritzler + * Date: 3/14/11 + * Time: 2:42 PM + */ +public class Address { + + @PrimaryKey(value = "ADDRESS_ID") + private Long id; + + @Column(value = "STREET", type = ColumnType.TEXT, nullable = false) + private String street; + + @Column(value = "CITY", type = ColumnType.TEXT, nullable = false) + private String city; + + @Column(value = "STATE", type = ColumnType.TEXT, nullable = false) + private String state; + + @Column(value = "ZIP_CODE", type = ColumnType.TEXT, nullable = false) + private String zipCode; + + @ForeignKey(value = "PERSON_ID") + private Person person; + + public Address() {} + + public Address(Long id) { + this.id = id; + } + + public Address(String street, String city, String state, String zipCode, Person person) { + this.street = street; + this.city = city; + this.state = state; + this.zipCode = zipCode; + this.person = person; + } + + public Long getId() { + return id; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getZipCode() { + return zipCode; + } + + public void setZipCode(String zipCode) { + this.zipCode = zipCode; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + @Override + public String toString() { + String address = "Id: " + id + + " Street: " + street + + " City: " + city + + " State: " + state + + " Zip Code: " + zipCode; + return address; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Address address = (Address) o; + + if (city != null ? !city.equals(address.city) : address.city != null) return false; + if (state != null ? !state.equals(address.state) : address.state != null) return false; + if (street != null ? !street.equals(address.street) : address.street != null) return false; + if (zipCode != null ? !zipCode.equals(address.zipCode) : address.zipCode != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = street != null ? street.hashCode() : 0; + result = 31 * result + (city != null ? city.hashCode() : 0); + result = 31 * result + (state != null ? state.hashCode() : 0); + result = 31 * result + (zipCode != null ? zipCode.hashCode() : 0); + return result; + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/domain/NoPKDomain.java b/test/com/perfectworldprogramming/mobile/orm/test/domain/NoPKDomain.java new file mode 100644 index 0000000..c49a8f3 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/domain/NoPKDomain.java @@ -0,0 +1,43 @@ +package com.perfectworldprogramming.mobile.orm.test.domain; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ColumnType; +import com.perfectworldprogramming.mobile.orm.annotations.ForeignKey; + +public class NoPKDomain { + + @Column(value="value1", type=ColumnType.TEXT) + private String value1; + + @Column(value="value1", type=ColumnType.TEXT) + private String value2; + + @ForeignKey(value="PERSON_ID") + private Person person; + + public String getValue1() { + return value1; + } + + public void setValue1(String value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/domain/Person.java b/test/com/perfectworldprogramming/mobile/orm/test/domain/Person.java new file mode 100644 index 0000000..b3e21fd --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/domain/Person.java @@ -0,0 +1,191 @@ +package com.perfectworldprogramming.mobile.orm.test.domain; + +import com.perfectworldprogramming.mobile.orm.annotations.Column; +import com.perfectworldprogramming.mobile.orm.annotations.ColumnType; +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; +import com.perfectworldprogramming.mobile.orm.annotations.Transient; + +import java.util.ArrayList; +import java.util.List; + +/** + * User: Mark Spritzler + * Date: 3/14/11 + * Time: 2:42 PM + */ +public class Person { + + @PrimaryKey(value = "PERSON_ID") + private Long id; + + @Column(value = "FIRST_NAME", type = ColumnType.TEXT, nullable = false) + private String firstName; + + @Column(value = "LAST_NAME", type = ColumnType.TEXT, nullable = false) + private String lastName; + + @Column(value = "AGE", type = ColumnType.INTEGER, nullable = false) + private Integer age; + + @Column(value = "HEIGHT", type = ColumnType.REAL) + private Double height; + + @Column(value = "WEIGHT", type = ColumnType.REAL) + private Float weight; + + @Column(value = "JACKET_SIZE", type = ColumnType.INTEGER) + private int jacketSize; + + @Column(value = "SHOE_SIZE", type = ColumnType.REAL) + private float shoeSize; + + @Column(value = "WEALTH", type = ColumnType.REAL) + private double wealth; + + + + @Transient + private List
addresses; + + public Person() {} + + public Person(Long id) { + this.id = id; + } + + public Person(String firstName, String lastName, int age, Double height) { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.height = height; + } + + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Double getHeight() { + return height; + } + + public void setHeight(Double height) { + this.height = height; + } + + public Float getWeight() { + return weight; + } + + public void setWeight(Float weight) { + this.weight = weight; + } + + public int getJacketSize() { + return jacketSize; + } + + public void setJacketSize(int jacketSize) { + this.jacketSize = jacketSize; + } + + public float getShoeSize() { + return shoeSize; + } + + public void setShoeSize(float shoeSize) { + this.shoeSize = shoeSize; + } + + public double getWealth() { + return wealth; + } + + public void setWealth(double wealth) { + this.wealth = wealth; + } + + public List
getAddresses() { + return addresses; + } + + public void setAddresses(List
addresses) { + this.addresses = addresses; + } + + public void addAddress(Address address) { + if (addresses == null) { + this.addresses = new ArrayList
(); + } + this.addresses.add(address); + address.setPerson(this); + } + + @Override + public String toString() { + String person = "Id: " + id + + " First Name: " + firstName + + " Last Name: " + lastName + + " Age: " + age + + " Height: " + height + + " Weight: " + weight + + " Jacket Size: " + jacketSize + + " Shoe Size: " + shoeSize + + " Wealth: " + wealth; + if (addresses != null && addresses.size() > 0) { + String stringOfAddresses = "\t Addresses: \n"; + for (Address address : addresses) { + stringOfAddresses += "\tt " + address; + } + person += stringOfAddresses; + } + return person; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Person person = (Person) o; + + if (age != person.age) return false; + if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) return false; + if (height != null ? !height.equals(person.height) : person.height != null) return false; + if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = firstName != null ? firstName.hashCode() : 0; + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + result = 31 * result + age; + result = 31 * result + (height != null ? height.hashCode() : 0); + return result; + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/domain/WrongTypeMappedDomain.java b/test/com/perfectworldprogramming/mobile/orm/test/domain/WrongTypeMappedDomain.java new file mode 100644 index 0000000..1dfb715 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/domain/WrongTypeMappedDomain.java @@ -0,0 +1,5 @@ +package com.perfectworldprogramming.mobile.orm.test.domain; + +public class WrongTypeMappedDomain { + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.java b/test/com/perfectworldprogramming/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.java new file mode 100644 index 0000000..3ebd499 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/helper/AndroidSQLLiteOpenHelperTest.java @@ -0,0 +1,48 @@ +package com.perfectworldprogramming.mobile.orm.test.helper; + + +import com.perfectworldprogramming.mobile.orm.helper.AndroidSQLLiteOpenHelper; +import com.perfectworldprogramming.mobile.orm.test.Main; +import com.perfectworldprogramming.mobile.orm.test.domain.Account; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.test.ActivityInstrumentationTestCase2; + +/** + * User: Mark Spritzler + * Date: 5/4/11 + * Time: 6:22 PM + */ +public class AndroidSQLLiteOpenHelperTest extends ActivityInstrumentationTestCase2
{ + AndroidSQLLiteOpenHelper helper; + + public AndroidSQLLiteOpenHelperTest() { + super("org.springframework.mobile.orm.test", Main.class); + } + + @SuppressWarnings("unchecked") + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + Context ctx = this.getInstrumentation().getContext(); + helper = new AndroidSQLLiteOpenHelper(ctx, new Class[]{Person.class, Address.class, Account.class}, "ormtest", 3); + } + + public void testGetWritableDatabase() { + SQLiteDatabase db = helper.getWritableDatabase(); + assertNotNull(db); + int version = db.getVersion(); + assertEquals("", 3, version); + } + + public void tearDown() { + helper.close(); + } + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/helper/DBHelperTests.java b/test/com/perfectworldprogramming/mobile/orm/test/helper/DBHelperTests.java new file mode 100644 index 0000000..89f433d --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/helper/DBHelperTests.java @@ -0,0 +1,42 @@ +package com.perfectworldprogramming.mobile.orm.test.helper; + + +import com.perfectworldprogramming.mobile.orm.helper.DBHelper; +import com.perfectworldprogramming.mobile.orm.test.Main; +import com.perfectworldprogramming.mobile.orm.test.domain.Account; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.database.sqlite.SQLiteDatabase; +import android.test.ActivityInstrumentationTestCase2; + +/** + * User: Mark Spritzler + * Date: 3/14/11 + * Time: 9:24 PM + */ +public class DBHelperTests extends ActivityInstrumentationTestCase2
{ + DBHelper helper; + + public DBHelperTests() { + super("org.springframework.mobile.orm.test", Main.class); + } + + @SuppressWarnings("unchecked") + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + helper = new DBHelper(this.getInstrumentation().getContext(), new Class[]{Person.class, Address.class, Account.class}, "ormtest", 3); + } + + //@Test + public void testStartDBTests() { + SQLiteDatabase db = helper.getSqlLiteDatabase(); + assertNotNull(db); + int version = db.getVersion(); + assertEquals("", 3, version); + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorExtractor.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorExtractor.java new file mode 100644 index 0000000..61c5ae1 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorExtractor.java @@ -0,0 +1,49 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + + +import com.perfectworldprogramming.mobile.orm.interfaces.CursorExtractor; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.database.Cursor; + +/** + * User: Mark Spritzler + * Date: 4/7/11 + * Time: 11:47 AM + */ +public class AddressCursorExtractor implements CursorExtractor
{ + + private static final String PERSON_ID = "PERSON_ID"; + private static final String FIRST_NAME = "FIRST_NAME"; + private static final String LAST_NAME = "LAST_NAME"; + private static final String HEIGHT = "HEIGHT"; + private static final String AGE = "AGE"; + private static final String ADDRESS_ID = "ADDRESS_ID"; + private static final String STREET = "STREET"; + private static final String CITY = "CITY"; + private static final String STATE = "STATE"; + private static final String ZIP_CODE = "ZIP_CODE"; + + @Override + public Address extractData(Cursor cursor) { + Address address = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + address = new Address(cursor.getLong(cursor.getColumnIndex(ADDRESS_ID))); + address.setCity(cursor.getString(cursor.getColumnIndex(CITY))); + address.setState(cursor.getString(cursor.getColumnIndex(STATE))); + address.setStreet(cursor.getString(cursor.getColumnIndex(STREET))); + address.setZipCode(cursor.getString(cursor.getColumnIndex(ZIP_CODE))); + Person person = new Person(cursor.getLong(cursor.getColumnIndex(PERSON_ID))); + person.setFirstName(cursor.getString(cursor.getColumnIndex(FIRST_NAME))); + person.setLastName(cursor.getString(cursor.getColumnIndex(LAST_NAME))); + person.setHeight(cursor.getDouble(cursor.getColumnIndex(HEIGHT))); + person.setAge(cursor.getInt(cursor.getColumnIndex(AGE))); + address.setPerson(person); + } + } + + return address; + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorRowMapper.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorRowMapper.java new file mode 100644 index 0000000..eb64184 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/AddressCursorRowMapper.java @@ -0,0 +1,31 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + + +import com.perfectworldprogramming.mobile.orm.interfaces.CursorRowMapper; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; + +import android.database.Cursor; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 10:55 AM + */ +public class AddressCursorRowMapper implements CursorRowMapper
{ + + private static final String ID = "ADDRESS_ID"; + private static final String STREET = "STREET"; + private static final String CITY = "CITY"; + private static final String STATE = "STATE"; + private static final String ZIP_CODE = "ZIP_CODE"; + + @Override + public Address mapRow(Cursor cursor, int rowNum) { + Address address = new Address(cursor.getLong(cursor.getColumnIndex(ID))); + address.setCity(cursor.getString(cursor.getColumnIndex(CITY))); + address.setState(cursor.getString(cursor.getColumnIndex(STATE))); + address.setStreet(cursor.getString(cursor.getColumnIndex(STREET))); + address.setZipCode(cursor.getString(cursor.getColumnIndex(ZIP_CODE))); + return address; + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorExtractorTests.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorExtractorTests.java new file mode 100644 index 0000000..33aeb43 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorExtractorTests.java @@ -0,0 +1,73 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + +import java.util.ArrayList; +import java.util.List; + + +import com.perfectworldprogramming.mobile.orm.AndroidSQLiteTemplate; +import com.perfectworldprogramming.mobile.orm.helper.DBHelper; +import com.perfectworldprogramming.mobile.orm.test.Main; +import com.perfectworldprogramming.mobile.orm.test.domain.Account; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 10:50 AM + */ +// TODO need negative path tests +public class CursorExtractorTests extends ActivityInstrumentationTestCase2
{ + + AndroidSQLiteTemplate template; + List samplePeople = new ArrayList(); + List
sampleAddress = new ArrayList
(); + DBHelper helper; + + public CursorExtractorTests() { + super("org.springframework.mobile.orm.test", Main.class); + } + + @SuppressWarnings("unchecked") + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + helper = new DBHelper(this.getInstrumentation().getContext(), new Class[]{Person.class, Address.class, Account.class}, "ormtest", 3); + template = new AndroidSQLiteTemplate(helper.getSqlLiteDatabase()); + SampleDataHelper.addDataToDatabase(template); + } + + public void testSuccessfulPersonExtractor() { + String sql = "SELECT * from PERSON p, ADDRESS a where a.PERSON_ID = p.PERSON_ID and p.FIRST_NAME = '?'"; + Person person = template.queryForObject(sql, new PersonCursorExtractor(), "John"); + assertNotNull(person); + assertEquals(new Integer(42), person.getAge()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(new Double("5.1d"), person.getHeight()); + List
addresses = person.getAddresses(); + assertNotNull(addresses); + assertEquals(2, addresses.size()); + } + + public void testSuccessfulAddressExtractor() { + String sql = "SELECT * from ADDRESS a, PERSON p where a.PERSON_ID = p.PERSON_ID and a.ZIP_CODE='?'"; + Address address = template.queryForObject(sql, new AddressCursorExtractor(), "12345"); + assertNotNull(address); + assertEquals("Philadelphia", address.getCity()); + assertEquals("PA", address.getState()); + assertEquals("123 Broad Street", address.getStreet()); + assertEquals("12345", address.getZipCode()); + Person person = address.getPerson(); + assertNotNull(person); + assertEquals(new Integer(42), person.getAge()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(new Double("5.1d"), person.getHeight()); + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorRowMapperTests.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorRowMapperTests.java new file mode 100644 index 0000000..564e3a8 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/CursorRowMapperTests.java @@ -0,0 +1,135 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + +import java.util.ArrayList; +import java.util.List; + + +import com.perfectworldprogramming.mobile.orm.AndroidSQLiteTemplate; +import com.perfectworldprogramming.mobile.orm.exception.ExtraResultsException; +import com.perfectworldprogramming.mobile.orm.helper.DBHelper; +import com.perfectworldprogramming.mobile.orm.test.Main; +import com.perfectworldprogramming.mobile.orm.test.domain.Account; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.test.ActivityInstrumentationTestCase2; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 10:50 AM + */ +public class CursorRowMapperTests extends ActivityInstrumentationTestCase2
{ + + + public CursorRowMapperTests() { + super("org.springframework.mobile.orm.test", Main.class); + } + + AndroidSQLiteTemplate template; + List samplePeople = new ArrayList(); + List
sampleAddress = new ArrayList
(); + + @SuppressWarnings("unchecked") + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + DBHelper helper = new DBHelper(this.getInstrumentation().getContext(), new Class[]{Person.class, Address.class, Account.class}, "ormtest", 3); + template = new AndroidSQLiteTemplate(helper.getSqlLiteDatabase()); + SampleDataHelper.addDataToDatabase(template); + } + + // Address tests first + public void testSuccessfulAddressMapperTest() { + List
addresses = template.query("Select * from ADDRESS", new AddressCursorRowMapper()); + assertNotNull(addresses); + assertEquals(2, addresses.size()); + for (Address address : sampleAddress) { + assertTrue(addresses.contains(address)); + } + } + + public void testAddressQueryWithNoResultsTest () { + List
addresses = template.query("Select * from ADDRESS where 1=2", new AddressCursorRowMapper()); + assertNotNull(addresses); + assertEquals(0, addresses.size()); + } + + public void testAddressSingleObjectSuccessTest () { + Address address = template.queryForObject("Select * from ADDRESS where ZIP_CODE='?'", new AddressCursorRowMapper(), "12345"); + assertNotNull(address); + assertEquals("Philadelphia", address.getCity()); + assertEquals("PA", address.getState()); + assertEquals("123 Broad Street", address.getStreet()); + assertEquals("12345", address.getZipCode()); + } + + //@Test(expected = ExtraResultsException.class) + public void testAddressSingleObjectReturnsNoResultsTest() { + try { + template.queryForObject("Select * from ADDRESS where ZIP_CODE='?'", new AddressCursorRowMapper(), "98765"); + fail("This test should have thrown an ExtraResultsException stating that 0 rows were returned when expecting 1"); + } catch (ExtraResultsException ere) { + String numberOfResults = "0"; + assertEquals("Expected one row returned by query but received " + numberOfResults, ere.getMessage()); + } + } + + //@Test(expected = ExtraResultsException.class) + public void testAddressSingleObjectReturnsTwoRowsTest() { + try { + template.queryForObject("Select * from ADDRESS", new AddressCursorRowMapper()); + } catch (ExtraResultsException ere) { + String numberOfResults = "2"; + assertEquals("Expected one row returned by query but received " + numberOfResults, ere.getMessage()); + } + } + + // Person tests second + public void testSuccessfulPersonMapperTest() { + List persons = template.query("Select * from PERSON", new PersonCursorRowMapper()); + assertNotNull(persons); + assertEquals(2, persons.size()); + for (Person person : samplePeople) { + assertTrue(persons.contains(person)); + } + } + + public void testPersonQueryWithNoResultsTest () { + List persons = template.query("Select * from ADDRESS where 1=2", new PersonCursorRowMapper()); + assertNotNull(persons); + assertEquals(0, persons.size()); + } + + public void testPersonSingleObjectSuccessTest () { + Person person = template.queryForObject("Select * from PERSON where FIRST_NAME='?'", new PersonCursorRowMapper(), "John"); + assertNotNull(person); + assertEquals(new Integer(42), person.getAge()); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(new Double("5.1d"), person.getHeight()); + } + + //@Test(expected = ExtraResultsException.class) + public void testPersonSingleObjectReturnsNoResultsTest() { + try { + template.queryForObject("Select * from PERSON where FIRST_NAME='?' and AGE=?", new PersonCursorRowMapper(), "George", 37); + } catch (ExtraResultsException ere) { + String numberOfResults = "0"; + assertEquals("Expected one row returned by query but received " + numberOfResults, ere.getMessage()); + } + } + + //@Test(expected = ExtraResultsException.class) + public void testPersonSingleObjectReturnsTwoRowsTest() { + try { + template.queryForObject("Select * from PERSON", new PersonCursorRowMapper()); + } catch (ExtraResultsException ere) { + String numberOfResults = "2"; + assertEquals("Expected one row returned by query but received " + numberOfResults, ere.getMessage()); + } + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorExtractor.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorExtractor.java new file mode 100644 index 0000000..fa75cea --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorExtractor.java @@ -0,0 +1,50 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + +import android.database.Cursor; + + +import com.perfectworldprogramming.mobile.orm.interfaces.CursorExtractor; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 4:21 PM + */ +public class PersonCursorExtractor implements CursorExtractor { + + private static final String PERSON_ID = "PERSON_ID"; + private static final String FIRST_NAME = "FIRST_NAME"; + private static final String LAST_NAME = "LAST_NAME"; + private static final String HEIGHT = "HEIGHT"; + private static final String AGE = "AGE"; + private static final String ADDRESS_ID = "ADDRESS_ID"; + private static final String STREET = "STREET"; + private static final String CITY = "CITY"; + private static final String STATE = "STATE"; + private static final String ZIP_CODE = "ZIP_CODE"; + + @Override + public Person extractData(Cursor cursor) { + Person person = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + person = new Person(cursor.getLong(cursor.getColumnIndex(PERSON_ID))); + person.setFirstName(cursor.getString(cursor.getColumnIndex(FIRST_NAME))); + person.setLastName(cursor.getString(cursor.getColumnIndex(LAST_NAME))); + person.setHeight(cursor.getDouble(cursor.getColumnIndex(HEIGHT))); + person.setAge(cursor.getInt(cursor.getColumnIndex(AGE))); + do { + Address address = new Address(cursor.getLong(cursor.getColumnIndex(ADDRESS_ID))); + address.setCity(cursor.getString(cursor.getColumnIndex(CITY))); + address.setState(cursor.getString(cursor.getColumnIndex(STATE))); + address.setStreet(cursor.getString(cursor.getColumnIndex(STREET))); + address.setZipCode(cursor.getString(cursor.getColumnIndex(ZIP_CODE))); + person.addAddress(address); + } while (cursor.moveToNext()); + } + } + return person; + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorRowMapper.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorRowMapper.java new file mode 100644 index 0000000..be9e21a --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/PersonCursorRowMapper.java @@ -0,0 +1,31 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + + +import com.perfectworldprogramming.mobile.orm.interfaces.CursorRowMapper; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.database.Cursor; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 10:50 AM + */ +public class PersonCursorRowMapper implements CursorRowMapper { + + private static final String ID = "PERSON_ID"; + private static final String FIRST_NAME = "FIRST_NAME"; + private static final String LAST_NAME = "LAST_NAME"; + private static final String HEIGHT = "HEIGHT"; + private static final String AGE = "AGE"; + + @Override + public Person mapRow(Cursor cursor, int rowNum) { + Person person = new Person(cursor.getLong(cursor.getColumnIndex(ID))); + person.setFirstName(cursor.getString(cursor.getColumnIndex(FIRST_NAME))); + person.setLastName(cursor.getString(cursor.getColumnIndex(LAST_NAME))); + person.setHeight(cursor.getDouble(cursor.getColumnIndex(HEIGHT))); + person.setAge(cursor.getInt(cursor.getColumnIndex(AGE))); + return person; + } +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/interfaces/SampleDataHelper.java b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/SampleDataHelper.java new file mode 100644 index 0000000..08c1f24 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/interfaces/SampleDataHelper.java @@ -0,0 +1,167 @@ +package com.perfectworldprogramming.mobile.orm.test.interfaces; + +import java.util.ArrayList; +import java.util.List; + + +import com.perfectworldprogramming.mobile.orm.AndroidSQLiteTemplate; +import com.perfectworldprogramming.mobile.orm.test.domain.Account; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +/** + * User: Mark Spritzler + * Date: 4/6/11 + * Time: 11:11 AM + */ +public class SampleDataHelper { + + private static List sampleAccounts; + + public static void addDataToDatabase(AndroidSQLiteTemplate template) { + int addressCount = template.queryForInt("Select count(*) from Address"); + int personCount = template.queryForInt("Select count(*) from Person"); + int accountCount = template.queryForInt("Select count(*) from Account"); + if (addressCount > 0) { + template.delete("DELETE FROM Address", (Object[])null); + } + if (personCount > 0) { + template.delete("DELETE FROM Person", (Object[])null); + } + if (accountCount > 0) { + template.delete("DELETE FROM Account", (Object[])null); + } + List
sampleAddress = createAddresses(); + List samplePeople = createPeople(); + sampleAccounts = createAccounts(); + + Person person1 = samplePeople.get(0); + for (Address address : sampleAddress) { + person1.addAddress(address); + } + + for (Person person : samplePeople) { + template.insert(person); + } + + for (Address address : sampleAddress) { + template.insert(address); + } + + for (Account account : sampleAccounts) { + template.insert(account); + } + + } + + public static List getAccounts() { + return sampleAccounts; + } + + private static List
createAddresses() { + List
sampleAddress = new ArrayList
(); + Address address1 = new Address(); + address1.setZipCode("90808"); + address1.setCity("Long Beach"); + address1.setState("CA"); + address1.setStreet("123 Elm Street"); + sampleAddress.add(address1); + + Address address2 = new Address(); + address2.setZipCode("12345"); + address2.setCity("Philadelphia"); + address2.setState("PA"); + address2.setStreet("123 Broad Street"); + sampleAddress.add(address2); + return sampleAddress; + } + + private static List createPeople() { + List samplePeople = new ArrayList(); + Person person1 = new Person(); + person1.setAge(42); + person1.setFirstName("John"); + person1.setLastName("Doe"); + person1.setHeight(5.1d); + person1.setWeight(175.6f); + person1.setJacketSize(43); + person1.setShoeSize(9.5f); + person1.setWealth(210000.00d); + samplePeople.add(person1); + + Person person2 = new Person(); + person2.setAge(21); + person2.setHeight(6.2d); + person2.setFirstName("Bill"); + person2.setLastName("Smith"); + person1.setWeight(225.6f); + person1.setJacketSize(48); + person1.setShoeSize(12.5f); + person1.setWealth(10000.00d); + samplePeople.add(person2); + + return samplePeople; + } + + private static List createAccounts() { + List accounts = new ArrayList(); + //For successful delete + Account account1 = new Account(); + account1.setAccountType("Personal"); + account1.setAmount(23.00d); + account1.setYearAccountOpened(1989); + accounts.add(account1); + + //For successful delete + Account account2 = new Account(); + account2.setAccountType("Business"); + account2.setAmount(560000.00d); + account2.setYearAccountOpened(1990); + accounts.add(account2); + + // for successful delete + Account account3 = new Account(); + account3.setAccountType("Business"); + account3.setAmount(475.60d); + account3.setYearAccountOpened(1991); + accounts.add(account3); + + // for successful delete + Account account4 = new Account(); + account4.setAccountType("Business"); + account4.setAmount(4200.00d); + account4.setYearAccountOpened(2000); + accounts.add(account4); + + // for successful update + Account account5 = new Account(); + account5.setAccountType("Business Plus"); + account5.setAmount(4200.00d); + account5.setYearAccountOpened(2001); + accounts.add(account5); + + // for successful update + Account account6 = new Account(); + account6.setAccountType("Personal Plus"); + account6.setAmount(4200.00d); + account6.setYearAccountOpened(2002); + accounts.add(account6); + + // for successful update + Account account7 = new Account(); + account7.setAccountType("Personal Plus"); + account7.setAmount(4200.00d); + account7.setYearAccountOpened(2003); + accounts.add(account7); + + // for successful update + Account account8 = new Account(); + account8.setAccountType("Business Plus"); + account8.setAmount(4200.00d); + account8.setYearAccountOpened(2011); + accounts.add(account8); + + return accounts; + } + +} diff --git a/test/com/perfectworldprogramming/mobile/orm/test/reflection/DomainClassAnalyzerTests.java b/test/com/perfectworldprogramming/mobile/orm/test/reflection/DomainClassAnalyzerTests.java new file mode 100644 index 0000000..b325059 --- /dev/null +++ b/test/com/perfectworldprogramming/mobile/orm/test/reflection/DomainClassAnalyzerTests.java @@ -0,0 +1,136 @@ +package com.perfectworldprogramming.mobile.orm.test.reflection; + +import java.lang.reflect.Field; + + +import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; +import com.perfectworldprogramming.mobile.orm.reflection.DomainClassAnalyzer; +import com.perfectworldprogramming.mobile.orm.test.Main; +import com.perfectworldprogramming.mobile.orm.test.domain.Address; +import com.perfectworldprogramming.mobile.orm.test.domain.Person; + +import android.content.ContentValues; +import android.test.ActivityInstrumentationTestCase2; + +/** + * User: Mark Spritzler + * Date: 3/14/11 + * Time: 5:57 PM + */ +public class DomainClassAnalyzerTests extends ActivityInstrumentationTestCase2
{ + DomainClassAnalyzer domainClassAnalyzer = new DomainClassAnalyzer(); + + public DomainClassAnalyzerTests() { + super("org.springframework.mobile.orm.test", Main.class); + } + + public void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testGetPrimaryKeyFieldTest() { + Field field = domainClassAnalyzer.getPrimaryKeyField(Person.class); + assertNotNull("primary key field should not be null", field); + assertEquals("", Long.class, field.getType()); + } + + public void testGetPrimaryKeyNameTest() { + String primaryKeyName = domainClassAnalyzer.getPrimaryKeyFieldName(Person.class); + assertEquals("primary key name should be PERSON_ID", "PERSON_ID", primaryKeyName); + primaryKeyName = domainClassAnalyzer.getPrimaryKeyFieldName(Address.class); + assertEquals("primary key name should be ADDRESS_ID", "ADDRESS_ID", primaryKeyName); + } + + public void testGetPrimaryKeyTest() { + PrimaryKey primaryKey = domainClassAnalyzer.getPrimaryKey(Person.class); + assertNotNull("primary key annotation should be found", primaryKey); + } + + public void testGetForeignKeyFieldsTest() { + Field[] personForeignKeyFields = domainClassAnalyzer.getForeignKeyFields(Person.class); + assertEquals("", 0, personForeignKeyFields.length); + Field[] addressForeignKeyFields = domainClassAnalyzer.getForeignKeyFields(Address.class); + assertEquals("", 1, addressForeignKeyFields.length); + } + + public void testGetIdFromObjectTest() { + Address address = getTestAddress(); + Long id = domainClassAnalyzer.getIdFromObject(address); + assertNotNull("id should not be null", id); + assertEquals("id for address should be 15", new Long(15), id); + Person person = getTestPerson(); + id = domainClassAnalyzer.getIdFromObject(person); + assertNotNull("id should not be null", id); + assertEquals("id for person should be 100", new Long(100), id); + } + + public void testCreateContentValuesTest() { + ContentValues personValues = domainClassAnalyzer.createContentValues(getTestPerson()); + assertNotNull(personValues); + ContentValues addressValues = domainClassAnalyzer.createContentValues(getTestAddress()); + assertNotNull(addressValues); + } + + private Person getTestPerson() { + Person person = new Person(); + person.setAge(42); + person.setFirstName("George"); + person.setHeight(5.10); + person.setLastName("Jetson"); + person.addAddress(getTestAddressForPerson(person)); + try { + Field field = person.getClass().getDeclaredField("id"); + field.setAccessible(true); + field.set(person, new Long(100)); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return person; + } + + private Address getTestAddress() { + Address address = new Address(); + address.setCity("Long Beach"); + Person person = getTestPerson(); + person.addAddress(address); + address.setPerson(person); + address.setState("CA"); + address.setStreet("123 Elm Street"); + address.setZipCode("90808"); + try { + Field field = address.getClass().getDeclaredField("id"); + field.setAccessible(true); + field.set(address, 15l); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return address; + } + + private Address getTestAddressForPerson(Person person) { + Address address = new Address(); + address.setCity("Long Beach"); + address.setPerson(person); + address.setState("CA"); + address.setStreet("123 Elm Street"); + address.setZipCode("90808"); + try { + Field field = address.getClass().getDeclaredField("id"); + field.setAccessible(true); + field.set(address, 16l); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return address; + } +}