From e1b1f3f9f58388005dbd8fd02215866b34e13b02 Mon Sep 17 00:00:00 2001 From: Allison Bojarski Date: Wed, 22 Jul 2015 19:54:06 -0400 Subject: [PATCH 1/3] setup gitignore on master --- .gitignore | 2 + MemeProject/.idea/misc.xml | 39 ---------------- MemeProject/MemeProject.iml | 19 -------- MemeProject/app/app.iml | 93 ------------------------------------- 4 files changed, 2 insertions(+), 151 deletions(-) create mode 100644 .gitignore delete mode 100644 MemeProject/.idea/misc.xml delete mode 100644 MemeProject/MemeProject.iml delete mode 100644 MemeProject/app/app.iml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82b1396 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +MemeProject/MemeProject.iml +MemeProject/app/app.iml diff --git a/MemeProject/.idea/misc.xml b/MemeProject/.idea/misc.xml deleted file mode 100644 index d415385..0000000 --- a/MemeProject/.idea/misc.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - 1.7 - - - - - - - - - diff --git a/MemeProject/MemeProject.iml b/MemeProject/MemeProject.iml deleted file mode 100644 index 29deca7..0000000 --- a/MemeProject/MemeProject.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/MemeProject/app/app.iml b/MemeProject/app/app.iml deleted file mode 100644 index eb0313a..0000000 --- a/MemeProject/app/app.iml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 4c7906491f6b186161e441eb576e4911816f255f Mon Sep 17 00:00:00 2001 From: Allison Bojarski Date: Wed, 22 Jul 2015 20:35:47 -0400 Subject: [PATCH 2/3] adding ORMLite as java source files and the required sl4j* jar files in libs folder --- MemeProject/app/libs/slf4j-android-1.7.12.jar | Bin 0 -> 7495 bytes MemeProject/app/libs/slf4j-api-1.7.12.jar | Bin 0 -> 32127 bytes .../android/AndroidCompiledStatement.java | 245 ++++ .../android/AndroidConnectionSource.java | 144 +++ .../android/AndroidDatabaseConnection.java | 414 ++++++ .../android/AndroidDatabaseResults.java | 244 ++++ .../com/j256/ormlite/android/AndroidLog.java | 168 +++ .../android/DatabaseTableConfigUtil.java | 429 +++++++ .../java/com/j256/ormlite/android/LICENSE.txt | 17 + .../java/com/j256/ormlite/android/README.txt | 14 + .../android/apptools/BaseOrmLiteLoader.java | 93 ++ .../android/apptools/OpenHelperManager.java | 282 +++++ .../android/apptools/OrmLiteBaseActivity.java | 108 ++ .../apptools/OrmLiteBaseActivityGroup.java | 99 ++ .../apptools/OrmLiteBaseListActivity.java | 92 ++ .../android/apptools/OrmLiteBaseService.java | 91 ++ .../apptools/OrmLiteBaseTabActivity.java | 95 ++ .../android/apptools/OrmLiteConfigUtil.java | 339 +++++ .../apptools/OrmLiteCursorAdapter.java | 93 ++ .../android/apptools/OrmLiteCursorLoader.java | 131 ++ .../apptools/OrmLitePreparedQueryLoader.java | 55 + .../apptools/OrmLiteQueryForAllLoader.java | 40 + .../apptools/OrmLiteSqliteOpenHelper.java | 326 +++++ .../apptools/support/OrmLiteCursorLoader.java | 131 ++ .../android/compat/ApiCompatibility.java | 34 + .../android/compat/ApiCompatibilityUtils.java | 52 + .../android/compat/BasicApiCompatibility.java | 21 + .../compat/JellyBeanApiCompatibility.java | 47 + .../java/com/j256/ormlite/core/LICENSE.txt | 17 + .../java/com/j256/ormlite/core/README.txt | 15 + .../com/j256/ormlite/dao/BaseDaoImpl.java | 1081 ++++++++++++++++ .../ormlite/dao/BaseForeignCollection.java | 195 +++ .../j256/ormlite/dao/CloseableIterable.java | 14 + .../j256/ormlite/dao/CloseableIterator.java | 73 ++ .../ormlite/dao/CloseableWrappedIterable.java | 29 + .../dao/CloseableWrappedIterableImpl.java | 39 + .../main/java/com/j256/ormlite/dao/Dao.java | 891 +++++++++++++ .../java/com/j256/ormlite/dao/DaoManager.java | 455 +++++++ .../ormlite/dao/DatabaseResultsMapper.java | 28 + .../ormlite/dao/EagerForeignCollection.java | 291 +++++ .../j256/ormlite/dao/ForeignCollection.java | 139 ++ .../j256/ormlite/dao/GenericRawResults.java | 60 + .../ormlite/dao/LazyForeignCollection.java | 291 +++++ .../com/j256/ormlite/dao/LruObjectCache.java | 136 ++ .../com/j256/ormlite/dao/ObjectCache.java | 62 + .../com/j256/ormlite/dao/RawRowMapper.java | 42 + .../j256/ormlite/dao/RawRowObjectMapper.java | 46 + .../ormlite/dao/ReferenceObjectCache.java | 174 +++ .../j256/ormlite/dao/RuntimeExceptionDao.java | 883 +++++++++++++ .../com/j256/ormlite/db/BaseDatabaseType.java | 555 ++++++++ .../ormlite/db/BaseSqliteDatabaseType.java | 91 ++ .../com/j256/ormlite/db/DatabaseType.java | 245 ++++ .../ormlite/db/SqliteAndroidDatabaseType.java | 86 ++ .../ormlite/field/BaseFieldConverter.java | 44 + .../com/j256/ormlite/field/DataPersister.java | 119 ++ .../ormlite/field/DataPersisterManager.java | 112 ++ .../java/com/j256/ormlite/field/DataType.java | 241 ++++ .../com/j256/ormlite/field/DatabaseField.java | 310 +++++ .../ormlite/field/DatabaseFieldConfig.java | 791 ++++++++++++ .../field/DatabaseFieldConfigLoader.java | 423 +++++++ .../j256/ormlite/field/FieldConverter.java | 73 ++ .../com/j256/ormlite/field/FieldType.java | 1121 +++++++++++++++++ .../ormlite/field/ForeignCollectionField.java | 110 ++ .../java/com/j256/ormlite/field/SqlType.java | 36 + .../ormlite/field/types/BaseDataType.java | 180 +++ .../ormlite/field/types/BaseDateType.java | 96 ++ .../ormlite/field/types/BaseEnumType.java | 39 + .../field/types/BigDecimalNumericType.java | 66 + .../field/types/BigDecimalStringType.java | 78 ++ .../ormlite/field/types/BigIntegerType.java | 77 ++ .../ormlite/field/types/BooleanCharType.java | 81 ++ .../field/types/BooleanIntegerType.java | 65 + .../field/types/BooleanObjectType.java | 53 + .../j256/ormlite/field/types/BooleanType.java | 34 + .../ormlite/field/types/ByteArrayType.java | 74 ++ .../ormlite/field/types/ByteObjectType.java | 44 + .../j256/ormlite/field/types/ByteType.java | 33 + .../j256/ormlite/field/types/CharType.java | 45 + .../field/types/CharacterObjectType.java | 43 + .../ormlite/field/types/DateLongType.java | 74 ++ .../ormlite/field/types/DateStringType.java | 99 ++ .../ormlite/field/types/DateTimeType.java | 147 +++ .../j256/ormlite/field/types/DateType.java | 76 ++ .../ormlite/field/types/DoubleObjectType.java | 44 + .../j256/ormlite/field/types/DoubleType.java | 33 + .../ormlite/field/types/EnumIntegerType.java | 90 ++ .../ormlite/field/types/EnumStringType.java | 85 ++ .../ormlite/field/types/FloatObjectType.java | 44 + .../j256/ormlite/field/types/FloatType.java | 33 + .../com/j256/ormlite/field/types/IntType.java | 33 + .../field/types/IntegerObjectType.java | 68 + .../ormlite/field/types/LongObjectType.java | 68 + .../ormlite/field/types/LongStringType.java | 43 + .../j256/ormlite/field/types/LongType.java | 33 + .../ormlite/field/types/NativeUuidType.java | 30 + .../ormlite/field/types/SerializableType.java | 125 ++ .../ormlite/field/types/ShortObjectType.java | 58 + .../j256/ormlite/field/types/ShortType.java | 33 + .../j256/ormlite/field/types/SqlDateType.java | 71 ++ .../ormlite/field/types/StringBytesType.java | 98 ++ .../j256/ormlite/field/types/StringType.java | 50 + .../field/types/TimeStampStringType.java | 63 + .../ormlite/field/types/TimeStampType.java | 60 + .../j256/ormlite/field/types/UuidType.java | 87 ++ .../j256/ormlite/field/types/VoidType.java | 33 + .../com/j256/ormlite/logger/LocalLog.java | 216 ++++ .../java/com/j256/ormlite/logger/Log.java | 59 + .../java/com/j256/ormlite/logger/Logger.java | 621 +++++++++ .../j256/ormlite/logger/LoggerFactory.java | 173 +++ .../j256/ormlite/logger/Slf4jLoggingLog.java | 86 ++ .../com/j256/ormlite/misc/BaseDaoEnabled.java | 136 ++ .../java/com/j256/ormlite/misc/IOUtils.java | 39 + .../misc/JavaxPersistenceConfigurer.java | 27 + .../j256/ormlite/misc/SqlExceptionUtil.java | 32 + .../j256/ormlite/misc/TransactionManager.java | 256 ++++ .../com/j256/ormlite/misc/VersionUtils.java | 84 ++ .../com/j256/ormlite/stmt/ArgumentHolder.java | 59 + .../j256/ormlite/stmt/BaseArgumentHolder.java | 118 ++ .../java/com/j256/ormlite/stmt/ColumnArg.java | 44 + .../com/j256/ormlite/stmt/DeleteBuilder.java | 69 + .../j256/ormlite/stmt/GenericRowMapper.java | 26 + .../com/j256/ormlite/stmt/NullArgHolder.java | 50 + .../com/j256/ormlite/stmt/PreparedDelete.java | 14 + .../com/j256/ormlite/stmt/PreparedQuery.java | 14 + .../com/j256/ormlite/stmt/PreparedStmt.java | 55 + .../com/j256/ormlite/stmt/PreparedUpdate.java | 14 + .../com/j256/ormlite/stmt/QueryBuilder.java | 964 ++++++++++++++ .../com/j256/ormlite/stmt/RawResultsImpl.java | 85 ++ .../j256/ormlite/stmt/RawRowMapperImpl.java | 39 + .../java/com/j256/ormlite/stmt/SelectArg.java | 104 ++ .../com/j256/ormlite/stmt/SelectIterator.java | 277 ++++ .../j256/ormlite/stmt/StatementBuilder.java | 307 +++++ .../j256/ormlite/stmt/StatementExecutor.java | 828 ++++++++++++ .../ormlite/stmt/ThreadLocalSelectArg.java | 64 + .../com/j256/ormlite/stmt/UpdateBuilder.java | 164 +++ .../java/com/j256/ormlite/stmt/Where.java | 690 ++++++++++ .../ormlite/stmt/mapped/BaseMappedQuery.java | 106 ++ .../stmt/mapped/BaseMappedStatement.java | 89 ++ .../ormlite/stmt/mapped/MappedCreate.java | 272 ++++ .../ormlite/stmt/mapped/MappedDelete.java | 78 ++ .../stmt/mapped/MappedDeleteCollection.java | 117 ++ .../stmt/mapped/MappedPreparedStmt.java | 119 ++ .../ormlite/stmt/mapped/MappedQueryForId.java | 83 ++ .../ormlite/stmt/mapped/MappedRefresh.java | 57 + .../ormlite/stmt/mapped/MappedUpdate.java | 140 ++ .../ormlite/stmt/mapped/MappedUpdateId.java | 77 ++ .../ormlite/stmt/query/BaseComparison.java | 131 ++ .../com/j256/ormlite/stmt/query/Between.java | 45 + .../com/j256/ormlite/stmt/query/Clause.java | 24 + .../stmt/query/ColumnNameOrRawSql.java | 42 + .../j256/ormlite/stmt/query/Comparison.java | 31 + .../com/j256/ormlite/stmt/query/Exists.java | 30 + .../java/com/j256/ormlite/stmt/query/In.java | 63 + .../j256/ormlite/stmt/query/InSubQuery.java | 55 + .../j256/ormlite/stmt/query/IsNotNull.java | 31 + .../com/j256/ormlite/stmt/query/IsNull.java | 31 + .../j256/ormlite/stmt/query/ManyClause.java | 77 ++ .../ormlite/stmt/query/NeedsFutureClause.java | 31 + .../java/com/j256/ormlite/stmt/query/Not.java | 79 ++ .../com/j256/ormlite/stmt/query/OrderBy.java | 48 + .../java/com/j256/ormlite/stmt/query/Raw.java | 30 + .../ormlite/stmt/query/SetExpression.java | 38 + .../com/j256/ormlite/stmt/query/SetValue.java | 36 + .../ormlite/stmt/query/SimpleComparison.java | 34 + .../ormlite/support/BaseConnectionSource.java | 140 ++ .../ormlite/support/CompiledStatement.java | 79 ++ .../ormlite/support/ConnectionSource.java | 83 ++ .../ormlite/support/DatabaseConnection.java | 181 +++ .../support/DatabaseConnectionProxy.java | 167 +++ .../DatabaseConnectionProxyFactory.java | 58 + .../j256/ormlite/support/DatabaseResults.java | 168 +++ .../ormlite/support/GeneratedKeyHolder.java | 16 + ...lectionDatabaseConnectionProxyFactory.java | 43 + .../com/j256/ormlite/table/DatabaseTable.java | 43 + .../ormlite/table/DatabaseTableConfig.java | 265 ++++ .../table/DatabaseTableConfigLoader.java | 171 +++ .../com/j256/ormlite/table/ObjectFactory.java | 23 + .../com/j256/ormlite/table/TableInfo.java | 250 ++++ .../com/j256/ormlite/table/TableUtils.java | 523 ++++++++ 179 files changed, 25726 insertions(+) create mode 100644 MemeProject/app/libs/slf4j-android-1.7.12.jar create mode 100644 MemeProject/app/libs/slf4j-api-1.7.12.jar create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidCompiledStatement.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidConnectionSource.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseConnection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseResults.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidLog.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/DatabaseTableConfigUtil.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/LICENSE.txt create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/README.txt create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/BaseOrmLiteLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OpenHelperManager.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivity.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivityGroup.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseListActivity.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseService.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseTabActivity.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteConfigUtil.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorAdapter.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLitePreparedQueryLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteQueryForAllLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteSqliteOpenHelper.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/support/OrmLiteCursorLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibility.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibilityUtils.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/compat/BasicApiCompatibility.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/android/compat/JellyBeanApiCompatibility.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/core/LICENSE.txt create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/core/README.txt create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseDaoImpl.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseForeignCollection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterable.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterator.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterable.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterableImpl.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/Dao.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/DaoManager.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/DatabaseResultsMapper.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/EagerForeignCollection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/ForeignCollection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/GenericRawResults.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/LazyForeignCollection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/LruObjectCache.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/ObjectCache.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowMapper.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowObjectMapper.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/ReferenceObjectCache.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/dao/RuntimeExceptionDao.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/db/BaseDatabaseType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/db/BaseSqliteDatabaseType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/db/DatabaseType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/db/SqliteAndroidDatabaseType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/BaseFieldConverter.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersister.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersisterManager.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/DataType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseField.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfig.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfigLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/FieldConverter.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/FieldType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/ForeignCollectionField.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/SqlType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDataType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDateType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseEnumType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalNumericType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalStringType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigIntegerType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanCharType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanIntegerType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteArrayType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharacterObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateLongType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateStringType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateTimeType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumIntegerType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumStringType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntegerObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongStringType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/NativeUuidType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/SerializableType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortObjectType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/SqlDateType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringBytesType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampStringType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/UuidType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/field/types/VoidType.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/logger/LocalLog.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/logger/Log.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/logger/Logger.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/logger/LoggerFactory.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/logger/Slf4jLoggingLog.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/misc/BaseDaoEnabled.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/misc/IOUtils.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/misc/JavaxPersistenceConfigurer.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/misc/SqlExceptionUtil.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/misc/TransactionManager.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/misc/VersionUtils.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/ArgumentHolder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/BaseArgumentHolder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/ColumnArg.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/DeleteBuilder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/GenericRowMapper.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/NullArgHolder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedDelete.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedQuery.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedStmt.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedUpdate.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/QueryBuilder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawResultsImpl.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawRowMapperImpl.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectArg.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectIterator.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementBuilder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementExecutor.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/ThreadLocalSelectArg.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/UpdateBuilder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/Where.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedQuery.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedStatement.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedCreate.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDelete.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDeleteCollection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedPreparedStmt.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedQueryForId.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedRefresh.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdate.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdateId.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/BaseComparison.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Between.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Clause.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ColumnNameOrRawSql.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Comparison.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Exists.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/In.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/InSubQuery.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNotNull.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNull.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ManyClause.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/NeedsFutureClause.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Not.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/OrderBy.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Raw.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetExpression.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetValue.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SimpleComparison.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/BaseConnectionSource.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/CompiledStatement.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/ConnectionSource.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnection.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxy.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxyFactory.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseResults.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/GeneratedKeyHolder.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/support/ReflectionDatabaseConnectionProxyFactory.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTable.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfig.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfigLoader.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/table/ObjectFactory.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/table/TableInfo.java create mode 100644 MemeProject/app/src/main/java/com/j256/ormlite/table/TableUtils.java diff --git a/MemeProject/app/libs/slf4j-android-1.7.12.jar b/MemeProject/app/libs/slf4j-android-1.7.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..2775197b19e625ccd3608d2be59cb3307a926815 GIT binary patch literal 7495 zcmb7J1z1#Dv>qB{NJ;4q86=d>0Y+-*h5?2KX^=+AK^g%;>28p2Nu_g;MkEYIkWxfK zd2qd6uX^u&@9i@)XU=!l`gg6p*4qEpP{F{w1h^>gs{9~-{Q2_-@B9u`(2)fxsX?x3 z{3(VDK%I-R>rX6kqX7UgbO3}=4NdUs8VGU|Xa;19qh0kX&HjFmew&Bz)AlwuT&R7C=aNbI( ze^WO_XOwgYA4GIioHFpz;7-cT)O|uP>nGT3^^y(I%arP%+MpeD&^=G0_7cIOGWl}d zOi`w(uL6Z`o;28~WxGvO#va{pndE|vW)enul(%)#QODGY%Q4o6BFk4m)1w4lI6hM6 zGr7Hmq2AlV@r9#L5=F4|96Rtsk@NyaJ0GZ@E>wS zKgroUxjOzOjQdBm;KblCb8G;B_c8#$^}DdFvz5Dxy_Kqqt*y1YtQFkV)7qWa(h=_A zk(mn9rqQM9KeNS~hqi_*f@=yuf%ydZ*Beuq3tf^a!oj!j3y}^ebQOa%$2FUp_3+2} z*)rF*a3=5%isI0B6Wf&vUz639DMSR3|2$^*O2UyAsvBFBX3X9jZU&n8wJW9?{3PLV`0XuY z=`;bK6_))Y51q>%>bONS>D8Moj>H)|PmYP};5{*Bjd`e`f&E}0MF~4D{96VLU-$7efvz?h56LaRpF20 z&?L&&d5%%?dNvj7m}+GYXZSHxo>jyF-+0bFoZX{6)?IotTnB_c;L3zm*9oSSv*1Js z`_f^xSMv9`p&WoEoVGWEo%C6t=uTFgiun;I?0BEN@coqlbR4&-ip^DaeF zlEex_LoT zB~sFiuu*Wg`v#Ba&~n}_#AzUF5?$%nSo(ghInD8m4v=YhVezTe;ABc+x$n^fYwk?i z{$a7xgZe!;L%aauHWUq~>`UXA3FoImtwNKTAsjXFRilc?0m63#6A!_&B{i5f^>@); zXFY66S`d4#As$Rz#S%g@r{KM)TsH|OBz__}r(z_}_vs`}eUcV3Tdmm^mZpyJq?#&| zLd?$|@mS7GLRQ{(Al}~&;v{Yiw!4v4LNzWMu1yh5@icEWh(YBl;ju%iz0i1G)^o5~ zu2)`>r6=C1@}w~5ym0u&Hu9rPJv|rGkiQe3yhEkk;cE^hDfwBE!P(>e%eiWgp84B# zo%Hl@g7A3Of)!}#dO{AxO7%mV2DTDhgyc6JPKP`dX{8FxrMOk{+6Ar>UO|5ST90(O zQo5ZM^^)bq_{^jv@dkbO6!@?tEVMc-^hArWm6bNEkTyU?Gq4E{2S!TRL*ur~;I3NsW?wZ|89uFriIg||vt4a%9N5hE-B-CMHKe8}?nH-#h&G_;BbHpfb*ODBD zGciYG%_<+#%#4Qj1PxC=fYido8H4N&MDnDNXVD<%3VsRp_* zt#JVg(%_rSyNZ}RtmqVe7!(*lmrDNALEd1$ zLXx&*cDR4C)jO2OrDBCWqY!}6#_LHAbUPAJ?!11Fid?BL^Z1S2ta^Q?8hMb~{0}59 zdr1cN>^rQ!J3NbJ?zDPK79wxkp1Ed`zIFb1hV&Lkoqdd>%9c~&wN!Z~tKs#y*Hy`M z^-MIdbXF-12kv<%9%cs4Ht3m&Q+WO?FS?}Iy|1M69tR|Gsmzj^V3vEhg@LJ%95f_b z&sADmAJa99ixdc{2TFqwvbn+^rN2kVZ(-`}sgN=e764Fko}K(}VG07b^mK9e{T8NT z^I(NGS^vWb*oFZl~ubu=;HGBt$%PRn7&%cIPA|J2U2qjhyac0N5s8cEtg zn2&+t-RghMN1#ojY6$^DdLHnJxjlI8$YIkvz;tcZi0Z{>MEIrznE}s!g6$?vd)hiY zOWkF^y2~Oh!kE-qrI}^5Drj4WH{1B(=u1u>3hgO$zq;~wt!dYF%M*E;_+7?c@%<9;5x`7q|a)x(BNgX|D+K^kxzhD`uo! zW-M}wHQ5@vl9E;a_u4qP!1av#LD5zYIQ=$&#lG-^i zX*kKduj!BNbcx38{YmSC>B^`!X2vCSi_dSIDSa1_8~oF0##ZSug{b&BVJDL*y55;I zanjNg+&wyaj!i_VuiI_yj!PLDFJ=iSw_3Glih*N+?XbiMD%HMXMU4Q4jYR5(MsOzc zI#t{-RUa1Kp%ssXl8mQ+A{MQ`S)_KF(e2w2bV-M#Lnh&^_R14hF+p>EUpbV83{y0_ zqktIY0^7aGd*r;k4p0P^F@&7RN~B~j>jaH^R9*uJia$b@nX>l=f_g`U-sdxPXg$}` ziFXQIm+PDo9tEKVzP}Q)ZBSt3EjV1N73+#+;)rp0pD=H6^@(vt?kf&~Ek%Lm#rNwX&~9#(wIg?};0 z?rzIxXRAgZ0Z`t&Zh5K$w5>y)=Dc;EHD|q}FA+|Kz9hOthPKset9%+td2)=)2ud6x0DQ~Y{DGBVos`!vjC}w_f;^4+C@+R8 zl5S*Vh9PZDw*iWS->B7N>_jWh&?T;mE{!jeWB4+J(+zO+o%qkHv1eE)WsmYAa5 zdOc}ms0voAe54u2{7t+MNpo9|`U`r`6jl8m>4_-$_v3Kp)_g^CQ(7PKzFP0V_z4&x za*{sxwooUx)^1rAW6#@tYMrSnX*}ZThx?TM@Ruq{feCX*TSvzH+clIWN>uRuU%YBJ zg;k8~`CjhY`G$kz_vByO6Ygnm`7QSc+dEr*PyJ)Hoz$l^$nI<}w@wfB0q#d$TJxl7 zjk=PA1x9aPwW#CDR5_Jzt(@V7skB!+_gm$l^p&cqH4qWnKWRBrElz64DCS%H)9-H6wa+8^UwI(PJ@Q5i_@79TOi{@kg z3a+?Y1A&{pAF3Qb#qk&(TpxG58VBFluCn*`^iG1W=A3?l89F7K(Qmhx(hCjaI6TOo zQr15y7)-ID!SsYKt_-FSRoOX>yGidD8g;7kBQ~|~AV%aj%LJ(NnGE?8EA> z@aOOhwKBIK?gS!;Q`rZW3eAH3wZjF&F1^L=l$Lu%lUMu-|EPp|2*CXmo=w%>1M*Xn z10E!0>ZyKj7pxo;_z3Zesk0 z6++?ex2)a&ZiV5Ai)xE9#E3I!hNA^AOio343Q3$p9BHKUq*O+cAmkDk9;K|}lck4j zj;>pFPTYHX3X+WEr9=}E;Fwq@JjKduGg-aDf@eNw%coy4%rRV`uQbIO7d4J-zlKBZ@GwngAT5y~%S>BL>tr97RcH`~0flI6Nz0cR`0w3ln1 zrvulY%q?G5+t|cM95Fd`^3&$O+Dh5iX&y~5g^646d7WAg@)_!9Ic6O$XfZ@Js$`*B zcW@6C?a1dNr*PGEyEHZis`(e{>RUfOu6ox0H129h4!HxukhyKGyc)K8TQNt^G~(V; z$~t>e8tBYK++AIPWkeFz-G_hA?Xc8AAfg^BOFTsv41z! z5e}q4(U15}Y%6on!b3Z1Z!Rp=)Gwh?4BM>K#}#cTC8NgQ%95fi+V7=J1aiqwO)fGr z*ynAK8Qv&BMdwXlt$uey%d9HlE8coy0GH~Ns4VrQlc4$KLwZ3gLvNtX`Veq@SEAH* zN<7NCOxirapEEHwlObYd1VbXi0^Nm`FI)Bmfv7=fCL%DpnglE#bdv3q*u zGi__pAcx(3M$V8Kd@wYeqPyRzwb2+^&pNrIg^7hF-4##DVQ&{j)G;!al_~gkYvgde zpE3wmaZBUAKHp=e;C2KS54Qm_0W2j4H|)M7SLEkp`rw)ac@n&G>2^B8R+_e+U>-FO znSR?*?>weri?qi*yvG#CLx;-di*cmbrb;Yymg7@RE@T<$SA#X*Fm1vcOlhNN+G)NT z>W|6(CFLVka?x(-TdrQD4O|hqsl?Jn)BZNXLQoUVz&XG4p{!##fyF3Y+O7AET9Rrj z(q5~@&vE{~c;!N%C&-NpV}2>yWed}UE`Ner&+;gx+P~)jSm#a?&22DUgP`plI;)$! zAKb@BeNy&(v1i|2grDqAt~4OY8*|2nHxZRZv;a%H-U?0(Xoc9W!xCx z<

DCE#9#5{oJw;hgt1X(@*uYf`QuXa`{yx$<*&dG}lRo}zRk0?njYoCUyiX2*)> z!N@*MYzzfJwsxTt-4WhrTs1YpX1^02^K=BIPRNA&nk?@Q(0RbOTY9GUGya~ zFc}*bD7f<)^VmV$p!i8Hb{o=*FE+fOrP6&2w}CL-yS_25BzGv9!9mX%*vWHC+`5&f zmc+ZjIP4heJhR{*)ljsUW%K@hMRDwEmXxSA&*xl;VY+?Fb{Qo8Ej zWYXDl+LXeE{$fhb$LI3m9U?=e&|PQZ;F8#iQF3iP*5|`Cv}P~O3i213Xw8=55VPKg z<`O0m9#YKDdqobZmxLug%~T=2tg!i{77f~ZlPqkOZHzC>EM3 zGM!y0kILx!Yc0Y-PORE!vX~LF7+|GpY-LOgc?L}G`~e8{Z5h&vjuQOv!I9u#UVEVmn{~S)g!xjXyk^k1QA8q>?^v~e>qrv~|zZQHhO+qSLg?rGb$HEr9rZTECf+kE})-raY<{q23P@>HCtsyJDB zA~P}~|0hyG8W;o$;IG&3YA>;WF#kg!zt6IwDuT3sO~;4M(l`q8w+|=W}rRo=~Ag~SVXxT)Ymu_hX`brOl-t#V$>6v)pp>EB= zeNH)nl8xz%?w^eGsdUoiXqf@#f^ta|@Rk)^qNXG1n*Gip%zQiu1K-a`u&`%7dC{rJ z-c-R%-v~3Fwq0B7v|jFPXCP{M4lE9G1=XHy-uDZykp93Wt8F(F_f__TlIjNdmr4Nu z%Kt<2Am1vrcQXIC(gFUV^#AFWf1Lj6vA-moZOmA#{+HZ;)`avoISW%82U92K{||Y% zzsWm0SpHiU;vcPqkshZ~0tWz~{1znso9w^p7BY0UG?q1VvNm<1Gqy2wc1~8;b;dqI z^;Jut)zrflcf^so5QF=yHKwA-pccoj83MxSHmIl{TG}zWq?5-gTu~`jvOjS+i`d>BjUYc$?RI`>y-Mb9!an z?_MTp{e1Q#BT&MC48fLk>kzX1NeTJtYA{Ceno{ zB9Vi1{)}OIG``{X)(3<>Y-)`{4@DM+KkkUs^M@vzUc!+GMvcf^Oef9B;1ay(t0J#& zRPN|)#i?H6QEB~61B71eZZw21-Y|VXM@@dY1y@QuHe+^yYAyT|e}$m8jm6Xl`Jx~jx-;1K6c7q^S!_}N*9oxG5Y?VtHB zxgiGa_8P)}>CW3?ay) z_WMAwiJ;uvc#HFM*0&+4+2xu-!j3Xx_Rw%(X`u;J5Z-vZ2G=I#+e*hREi}iU7Z-uf z2O~<^S9g_uX8Q9tyiMnLDjCaY#lY1qQGzMvtYL*ddF*1*n2hb^E zEG=|H%=N=L6+C~x?*OB(rH^?INx{$rShOoRF&!JH9Oji5bhAxlbK!x_*=pQxM#Q72eqP(94dJRv%^#u|?l}PQz@GZp+eX6h4XjJy&a6qcl`n zoJoD5QF-jKNjn(rfhn{}G5oPCbQ2ZQS5l*fYx*Dg=i>24_ryB?q?BZHN|U_6t&no_jw}3ZtCZ~2S58fVuQK44UkbPP z+)5J*t5IuivrCP{r3c0wzJ+Ld1XB_g}1H{nGOEM4`UdL)&}h3%|)V@_2_nquROu_{0=@uLI98dk=Lm85B6%fe)5aCH{66~5m?O9Vs;GKbg` z=HecP8u7$-n!!GeP&UcrfsAF!SsiM870CS1xKDy5G@=_k9FgoT$O(B9eyEC)dg83% zB^X8Qvk6~m6Nu$1eLpL-=qgiS#w_aCJd9UB$GT5Ss$gxujn8>^Z1^ht{gLP9leM*WPsg6c|B7Z zUA=>o&p~)MkhYZsNso}lAAkc_8RDm?>k?cYNMo@eGD=I@x6p%hR6h;iAv$F5ES3Z! zA{p2n3IOrzh3Fb`d}U{Q_Ojk-Gj0%_uepUjy8_VYxvvdf>J($6ts~y8?gZ7yKmled0ftpY-#_tS5m*_ne|x);*eMbAbL+c*_=A1M9{mC8kA-br?k5SuWxdB4ExHam4ajoVHI6$dr$fKZN zK+=_2-m6{s+i0z2#w}8kh z7&2(K6E+M&kRg!}&`n>16UI=rZhOqGei1y6MS*6V$d71zlQHuE-WI=o*Q`K!M0rdb6WdjY9G!7-nP*=^aL9Ms6 z>o&BEmJ9e5(%&d+R`J;FL!squrPtdF`kS}MGiG&=X?;%c?4wjnnnqrW6H{;#Vbic{$t--d*b`ynKn~(ZDf!UMN}ue}wgCt7)g$&u*^sqPHRXE@82_&rIO- zv;b-Mh=Q&md9+16dIaD1Mcn6BMG$*2L`1?}akd&0r7<>z;HOm7HYx9Z6j;O`iyXp4 zlb3Bky+*|OhV%$U;S_EM>R*=@n4j_r8c-{V2#>#@@`hYAeO1Bc7T9s1eq8*9@0CA* zsUsVePCz+9><0>|4dc*L_(iL@QOVKWkAfNs?@rn8mQBIksNQu-9Lsw;!uxuj@5+Cf zs#|JtmEV@Ev*H;$ZimgwQPCc)0#sH;QZ$i4iXZYd9$|NcJ=45IFL9ws$>a>KX^a?})KE-~qZR+X7%D}cmwOotXme)4o28^2FS25a}wK}e-GDkU65 z8(l1h_NdoTkKa@3TZSxGjnrzm?Zop5fNKRmB+>2;3W`jryyi0%4~O*5Mr3) zrOh`a;TQKmcHCMdi;Qf@aFwHw4r{Als3`6r*)|DGT(E`Y9=TIvGsV6GhAz)CLt&Ya z!&SBAWp=bN@sOkC$PnGDwR>w%DsU>CV81|kifj&uY zCiYNPyyLhPp$V_+K1k+Ig?JkK@n+v=vf4~`dKs+>wO8FB6enU?R(U-~oChK09ywDI z>2k@6QB&A6WhLqE`wd!~G*}_MJOu_2qGSzZj5K;j zFGW@-9O%Od0l!*H@`BExz^=Pjy@dPQlPQ%QFxXLGi1YLCDQ`ySz%dlB35SEI=sRX9 z^AFNTdWJvF)<9_oTpHDz*Fbq6P|jV(+!{r@=~lDND|x!OdAjTWl%*Kwu4Hd2$AIrJ z(1!mWI2x-z(4v;a9AjR!~OO^2*GsZ zY5?5`+%0h7y?cb!!D)0zD&+o*SEKlj`lMqSQ`#qU3Dp(y}(1rI4Mza~$Qegk1K2Ew+ELg_oD z@8{$)DQ}uv9pdK-lRzd(cBTuqaD_S!k~{h&JZdF~3C^LFO(%#mi1TB8y-ykwJE?;F zF%5WR5?q`TW&I|>1Q>Kr+b4FZKp&_9j>q|d0NA9PqHpjPNYY4=PtYbaR?5NF*xaBD zXT0*&K!uHU!H%v*;UdLO;);FdHiwo7CQ7Jla;Z^%;SMOojc6v&K2VFtVsT?68#IQl zu6!lA@}t+34}8DoZRyX&)X&6z=wA8Y9h`E=J-_mVY7D$>YkgEpsmtF_%6yVaHIe#v zi%(AXg>;K&V&UCGwE`$F9PN{0dO3-9_zB+7L%yN19awAm4xkUT&}_Q%SnZ@jccjC< zhiHo!B@IaIn{dJ)7MPPlomZ&>VI7^K48QwV&lg&xs38i@FzF!?hV6Q3Zb*q{>J;Lq z=jYA_M=UEeF7yu z6#k4)@pvCDd+;Ug`2+#_9emw<`-lJT8^r9LYzd3-NvBJiAnVTJ%zl}E%D$Vj`Fj8Gg){*B1*KwwLXj4soPuho6@=-?fIZZJaf~9u zh(F{E3at^fAIy<~o<|yT{dg|))39>4^~h$)hub}g_s}f3W9g#gK&$-()6KI*jq!y^ zcyWKJLhl$RJDB(*JBQV^BWvqMr+?OQliMZx>Gh|ie-5_))-T)sL6?@!`5r-yU0(@dDm`wJ&Z{T)UTcy7djaD6Y=(?yxr-J22j% zV#$W2{xkx7wU&hILZm4}(&uu)T=HbVR?0sQC<@}Vi-B4&HF9|F(@)DLyQ-*#I}*zY zq_rU$-2Q0DGiivBF4_&eb!+RiY|fy{?rbY4O%`8pu(ph{!z!-)I2kSl36J1|3C}C{ z@Rvi?Jyn|v)I*JBoMhNk7|$f5KR=Es_4YXsj#uXy$S+rJHa4%=tj2QTg|t)cJP0}< zyU4I4J7ry@vGY!v4H7EJ%|EydQ>6JdY8Y&I*fXOd%FSixc!8tNYd3e7geB@3sCC;N z`{G~aV+BuqUrce|6pVQt3N4jsxl{rLlT-2&o+J%OHHzc}z(85Lr4y$PFa^fpI5;hCRkpEGD zNY9`D05tb8y^6Wd-!$Ps$^HyHo`NP73N|yB@aeR*2#`TZryv|D(jnqYyO6}m(-&bw zp;-FB`-gl6*#$g`0g9CttKf?|>dn@qSYANnEkr@MNYk!bQm6^;n%soso+XdNsl|i1 z)&pqFfB$-G-$%ePs#@nRa^xs3sMWt+zNYYjdR?waAka8zzvxi*3;6GrM7pLt$p39c z#ov(Se_zJ@6I7Hmx3hQpwx(~K@qdh|IMr4eSsi6$CO9aL1xj?zDmSkQ0SE)%f)-5F zQz32?i5(iMsg*=pL&ELsQp`qm(dZYgZ4*56pQvn0^Veqj-)Azvl!meJ^WXRX` z+WWUeUvCbVoL}DVewj9(FHbW)fV&hv=mBzRkb)?D1Z`5cgkv;gtVro5F(k4Hgo+Z! zlJz9eBvfNWV@Q%QB=QCErz8_3`0*GdWZ|Mp1Z{HlLQa8)2LL`u0ch)6vaP5A8?{mc z+z$?^{l6X@Vuy3*A=Q9u(fr>4pQ8i|%P}glGF4!dmsl-b!T>vZ4Oa51(^D!n6P>E_ zp6zRRlZR`X`3)6TO06nfGX!mmj1zv^E6h;9U?EkMM4{h0N5m(bIB_f_%`RxD4exW% z$-h>DE}6`x%tqTxPEI}145KA&GI@>AjSnXnMVYZ~DKuU9+fX@zb*M6%j#`>(6u!d* z9*>qRDoJ%3W4R}{yfxF}oA0)iK?J&mUJMra=*AqG&|)Lg*2gT=S>2hXMA$+yomN$h zmS7s%DN1U)t+jh@jZUB~r@~hIdZGq0#y(`@{;+UqT*NVUsIOROo-Pj1D2!8Qfxg&SI3}DxFVRh+2|vIg3K{{0f=WT9ppthABASRItHdHXNAk^di73Vw5Q&OL zM4+HjW)zf(D25jxiIPT0)y8BV&#)^fIAab$^r{;^V|7FJ7Bc9Blr!*z*xw9`TrVJo zNTwM%lx3Kw3Z5^Ux;!vJ@@A?=&1Vcpvld&vX`8P5PU7NExP^L}Yk2?CI?KX#9Ch_1 zEBLa%@pR@YLHE78MwwV6?NXxA|VSy@!lYy1V#gq;bo*2Xx=X|!TR zSyi!ELA_F-$Dh@=r1D_XRuT5=$0}HX?w08FjcKg0QmihUmX;bNc3Bc^ufJ}JS0FS?>Z9A`y@0qngG;DgCQb_6C3E?*&{jdm-%a92bRA* zesScKT!G3lL!pnCVmh-xT!VdBIGAY%entoa(XE3W#MCa{)xy{TNAAK%$L$<@PGXj0 zq4j~LAn3cnBqHG%AnF);hPNOfq=e)45w-Rr9ao9)$c_Wk1VGbtcmz_iv|a$FK#FU) z-PKOo087tfrhxfoHl#UMS!pED)w0~0E~F*h07%cHrlfjC()8EY6941-8#QT$uME0A)?*0Bg4I8tq($_%)0El1%08sqb#jdQWv$LVO>Howv|61)9t3&%JFJXS=lG)DR z3rQ0SZ*b_1u;it~t`ny_mqT%uF`N$Zi8xNH-gGRNLt+*@ zr$b~GzOzAT7QXwg9H%>Ht>83&f)T#N2(pJ##rDCVveHOww#RyK?rtr#8_&UD1LiYj zPU66wTqnUvF6T|bp@$?Kf9e4J;dMPu499hZfY{4^#rW@N)}~@=YfzE&-ne=Pii%%^ zKa#O*mTc~K)d1C>nvbYSOfv<%*iDNC+?LE-Beif;grkR8y+{zOvgF4it;eO*X|^?* zi(HBiIn@dNGzw7Ur9!OULaRZpPW1cA5;RaD%%?Q5tysouHPMwxdhU6#wn{~V9Ig|s zKa>cl7&xhN%xmDlvQ4h*Y!-oPH%c_NYDJ1IiB%EdSgN_|Mk;t~1=b;yP4Xk^(2w7? zPib}W^$`42k;cKa($)^Z3Wl>~L#<+H46+Etvb?bFCY3kPRr2bgLgGf1XD`JBx)hVG zv5g3tNzI{B7XI|tUpiy5|7;h5ib?cJqnMY}ERGLbCu86N#3sg5DSX|J!`q92QN66J z_332!Sp~^TZk(|?=||dCL0&t$1@D2F$6VlA1q;MUZlJk{1@RhPiJ+Q<9YzGGS#)^=a0BlBq1f zNfXG1l(tr5oL?}OBCRU4oXQexZO^;K5^&$XRg-CVB^Dx@g9t0FNB~VvH@Ah<&c3Df z8C@RdkCvPi0wHoGDU)4mplCu>`)Gx*uV01oYWXZo)D>(NWJ4}_0&@l|(wJv^^JQ(Z za>gle(3!U;$8ONHXZjXB7H=X0Xp5`hK~#Ak_yu4G85ivi9A)MCd~Q+L0xw!)$@MW~ zS(+;)){=L$OfP8hHQkcYBva~Ux5;A^Y{A4{^HHp1#<1A71W7#F#bd)M@t827Sc%8l zR=2dI(;)^LvU-NWV;X%)x7e(?Eaq)vn$y5dWSdy!D^x)BOj|eL8u3Yac~DCr=(*rf zY(HPvcm&B+YUOeokh>daq}FQ2_gH@_e)PKTx`Or$)*LqLBR?+NLiq-=qr9WpQ+Yu5 zC|`TysMcbhP(qCD(RRw_;>=u-S~Z5RRT03zUG2i#%;zr~DD>gqkb{3#?&@}y?gBMZ zwxXm`uKuJ((Fy*AsuOvRvgwbFx*1hN-HO^m*^28<(F(DF`ln$Xf!POqvEis3PpRSOMM!2?`}n|7=NC{6!v>}C?|8T#wI!| zNX&G7O-+Y=J}LDosI(M_Ie^mohK(g4;#=g1=_z__34x5cT$cr+M_lqNVbjj4>voRL zSb;!^kV%dN7oo-W_gQx9H?JDn3hODKd{H7^_Ber74^Y9=pn~!#l(W2PQ=Wqk=!Pq> zZu|IO1`>LBGVNM^dbT1}p@8=44|{RdU_x`uP0g(U?z#3s7S@)w$co|HB@Q(BKu z$a{)HJe6r3tuKPZMYavY?$a{8Mc@Xogo}K%CQ4D-14e>LJHtCjL1Ld6vbL+YBv8RS zx9K|k#FDQhSKa&0yn!Bp_gkwO7r`#}N)L5MdwE?p_4>Qe4;=LA6L~aZVY3<-QLt$f zJ$%!(lm@rbNgn6CA5isg0mfPu6cn)>CU`P0JX;mi)wD%dXrg=mC2co zi`S2f+7^6t->#F`wa3nT<2c<~cunc$rZ;ROZZeFVl)5|k$0Yv&y$21xz4B584(WYcAR#xF+bY~RFlKv^<+H0E0W;;L-t2_(h4vDw( zuGnN7+GLw8!L=;-SPkItpQq^X!>>Px!_$Xqy9gf`!kD=+^r{c6I~|1J86U;#2XQe( z${Cd5i3a7EWb#M}|G+W6<2m>wn)#DD85iI2gc7i6J^;D>9OIV4lgmgWdKreB{+1{gn! zQ4R^yT<;GyBJL0|Gd-$f1uOqX7NV0M{o`X(o5ao@_ZlUIwmt5ZE-z)EZJH;Sq7^@S zZ2ffTd?mKSXp(7?xnokV~JXdM@g>)e8g{zewi*Kl6i!%dpE+o3hIM9aWR;& zQ&tAo{W&#RJGw0JTGplWV?1m!#t%QB+2{B)!q|zymbljtf%jvSu^0g*k!uV?eu*?x z;{Gj}WrIk5hdz+xMnmjaA(}O=4@2H`pn`NqYsBZrh|*}x6s{L}dPEPV&&_mH5!cg? zQ8ET+$(+>ySZGm!>=qvbvll>w5``knAO|ym5+9vz#Bt0RngYR|5*e>SFV;sdMoi>1 zOE$bq#fZipp|eSP>yV2P8fe60)E_utM5KqN>?QgT7B9?@({1M3N*qQY<$lilp{b-L zxn>k}% z^Jq51Xkj+JIpmPglV}^vhCC10Kk=Z22RwlGV%s~p;p{R9=9BWVw`|F1;Dcc&x z-J@Z^%1QGK1xto}&Xym%&h~{e={2rfbAAiST_?({)BK5aJG*sPk{3_eodxzF4acC3 z*#}M(Vkv?AHCxDSU8QUbuNLY0B)R)L~fhbr`cF(#nDet68`yuc7j)IW`Tk*%Jn1 zrkIW$$b@-XT0gn(CJCf#pFv)ZXc;wRQ;32!*WhvJ<4=UP4;}W!R9e$oUmP?LQUHA(wJN7OnU5ZiN{!%NoGXmc)c?i53C!?Z_7I zAXp{J0EJ)?tvDddRz?JmPHdPIBEcY8g~}9#mLQvw6nfJvB$ud#ml*0$6FJg8wJ>NR{{eHZ`?ZVl4mhD^NqS|<&H1p^TG)JD(x#?WA zxAxU$LDPD!BXgxvaeLf`{p2;}*izH9^Dy(`$qpO8-g?77&kk&XksBTfFl5ELA;v4= zk?yaaW0HQvsCY6Zn*t7Tn!P}5tO5hkjW8kON_5meMeoejs*kWFDeZH~LC0lI;v8@* zazQ#ioA54lpqLk)M|HC}Bv6thx);uY&Nw9Ncd3jMo8^%lr=)$}Z5vhfY6S3F#t&ab z81;UqLJJMK%1Mb~QaYOzN)u#_rGiW{(h?D2%#V*kqmFDdhXWQ~?)U7&)sP7xTukCb?jW9mf>Ge82nHa_T1qLKULJWui^e`R<(%`2-dqE2yR-@i_d48Jd7DQ-hB$QQK z{C+ru0c4WTGt^Rc6;_ERty4(g>FKfey?ke-Muwm%s@aw+J~>8Ofw(VXp&Gdt@^ztY zrb1<#B?&pFluaS^`swekq=_t9IY_122T~cI@qb%mzt?iaA~Ev8??}nPaGBS|}PT(u_ni)H^R0H9Dpc?>>0FE-a0ctp)6OyyTG}mUr9i|Kxn5|mpda8$d zXjnA~?KGCqF1?7-San6N)#%*7PI+oAU{M>ZcX>HNZg^CdnV zcj;kl8Xf}UM){u8iqF9$A+%DL(#JIpv++eg9+w$o^#s;`iz%hNGeqy|DJHEI{~Wz$ z$2L=g!DjxUjH?~C5R8H3QjI}qD|dw>8X6SEpy&Ne^BHBrj` zXNHK9;&=WP3ZJ`ari=pvh@xT|0SY4vRw)U=@Z{-)0U2}WPO|hF%{OXBcd37t7!Lml z;FolBt~)ZWRk)XblI1=9(tWqJad-X{n=5i3)l5}1=atSje%~sS86^eYY#kaJ#q#4V zXijB;8#Steb^~=}PE>tVzVnV%L%61VmN?I`4rFM|a#$ z$IVZV`|6j+q__OIx2T8`;?_Y7>7XdK2esIzwmpXxCa(NOU28w?OrbY(#<0+n5j~Xkn4c&hGVV4L zM}{PQk5=`**ZlQxeN5Pccf*NRuY-9SDnl}^D$G(T)t^~E`CkR2ts~YKYCu(#xb;Y6 zZ?@I6olRlgj7MQm%LgqU@TmtZ?0E9F{y-x0dwaq_`D|dgD`!J#*w~OXcf!PC)|zI) zc!25q+-9$`gMhXJYAZ=*ml&Fdz^deDUc;OqdWdIJLT^d}u8}&V*pc?8UI$|gbjuOA zqdHk?$i_LU6mu54deWNjuRf7NkU?BLvtXtXD$l1lx#nr|1f4kn|G;aqTQ_SHV`ZekLHIlS>r}87E zUb4()YirSsMU@P;CKlPca$C#bc1bz5OuNU!O)c9t2nvW%QH7v@Jc24AQHt4&0#LzU zXrM!w*>jj*vwzWlGWS2Gb8<-Dvac@A9%K1V>UZvZcHVS-GcU{CV{-`#Nblt$yv~-z z+9T|w>wK!im*-9hTZAwDeE^-v59vm?;^>7ge{Mpz!8`FEO2>f{zzcQ5yg(fij;a&F z3*-I7@#lf^gt=uMJ%}SG&=2(iz9l)N9Azhz7lspp6YK_kVLQYeZHOC)yAsF=^g+Mn zIRqU=9F2@iC7>6M5sDG)22T&d3GD=W;X4G6Lyl7?tTFuUn^v@^9Lr8se7)AG~qxIxX)WKE-4 ze&&QV5t~E}O$xL_d-lR@ZKp1I!Ta8jt>qC_N~?#NuPj~LDR9VXQ=z(*)g0~mqAO~x z5L9~hXrUczDFQje(iA*PaDr|%rs^e|{4Qhb3hs0BF(z-X1*MYK%uYgETj;$u@AWYT zH(;Ml?{=Xz8NHFf;BaBv1`B5)#HJ->Fw8FI#D?v7@_iEXP76{CkCK*yf4EM1JheDr z9XP=a3>$lGFUp!U^TbX~09!Eq+Rzr0^`R?`m|e~xyaAZsb;Z-l_WSa!PNcMj!B%g6 z=S+{>4riJ!*lqd=;wG4!5Li~Pzy&oCDdo!1ac11{Nz~ow4l!((+t1->_ya`+=RPfc zthtY;Iw1bGnwF!<`Ni|D_ru{zR@tqrh`H<0U&WYm)fTEa8w&>~M=0&y7Na`4J*&lW;U2Lun{t_r!kyG4`>dz=IS|DLm28AucuQiKcbdP z&%oLlWpneYEZ4=b30H)w1AU;Fg5<>=)cvCb-Gn3JZdj6cm-hZuX#0K*{Ht&S*8IC% zfpmiXyO4FF{k!~co~;w^-{pVv{H6L38T#2pl3vuZ-WK*5k8__ay0X{__ih=!l08T+ zaz~fD>dBF3m(Y4MV8f2`jOKts%pMor7a53Zd;kH*;{)eAE>mZlC_7ibHyZ7>)~5oF zL47EnZ97htK8?;v`*yrs7ejCd{CFzG7%x??#nwmHXXLv!3u0=@CmE5_r&G%>L47L< zWh-e_X4=Dy8q=^SrB|*OvYf16Ic&+CuHvT6gTO2zQ`22@!}rru%Pu|n>GryHeh(K- zz;%_{Q1m0e15;@9e1skVGz zd3j)&bF0DQQ`R%!p8+-_`U6InRV31tv^q7dYPU~%)a+}N;no`7x*?f+xIEfDJ<*+F z&OW?y?DSY5DK%Vly=3#pae(<~*w>>0e_LjyW3P2%$o;_u+8~cAUyv?8_1avi3CW4& zG?}4j+%Bo1=J*0O;^L3*BZH+KTj9ino@!l6IhQ5(J@qo9eBm}1nhLOO0(g6RHgLqx z6l8h$HXcahaaWm%^VVd6on2QRTs^(HTdqr&6~wKJ$M(7B$-$TP->%MrHVGLAO5)tl z7nd&FT55Ke?u)zXTIx+%8RJDRdrIk5+BPAM$e zHNR~c2wHjZu756bZ}>D?oAP9~Tyk@)oG zaO%0R>P;t0uMUu2L`R|PMyIYhJ@QR&<73j3lIk6gdbE{8{X--X+>4sqJ~9fO7li&~ z;@pE<`>G6(-itzyF7Cz&{ov@bp+6EYAQk#({PO$w_5Bls-tqVrPwy6w2CyEPiic-S zRB9;DjSvvRO5j!qSI zdc5Lzdy2m?qPnTq7@olYZj{miDK-3k?8UpXNv8&(hi6Y`jGx)LzEy7|YD6k2liugJ z$IFK1-VjvZgi>35kF)95w;I(T^!Nze@8mC!^;vg^)b>o%qf-6A_lFesj{?6h8CdyJ z>x(QLo=E;~S8DdxcQ25G(BmhVsV*vNEw1>Zi$|xzOOu9yu!ftMbaGgA(xf&hXKkTi z(l;Z~QR)_*kz6&=hnUm6cp(apLpuhJ-T6(UeO^=ZWe} z)*r`SfhXrnJ*_~e3fW#^dIlcX#NWCMX&=f~;vBvIly4p`_}SZCGu!83^L|Oq>3@?=ZLad#?fge+_g0GdB6#0wh}1_OF1~_j3EU6ew81E;JO;CG7aG zfWW;pBuTb+24SK7RJ$Z48rm~0q|cBZ5#*@=l41Fb=fbEHH&^V9xF4%sr6)OeStp&F z9Xo#AKfoDcX%T@XASEIp2v9}&&0ZOxHG8wdAdX)~0{lh0>BGZ}(xqpfZ*_Vj{8a(z zF+c|9S(d7qWKd(0lttZex8kT%G#zcVS#ddc5F*DVLkP9zS(P1iv`syB0w=V#h81LF z={TG1tkMHat(}%QG`JGrRS$v+CP-U!TwO+77XwDf6N?q%S+^FmX})3JRa1zgsvl5X z3bV4VBfNCK9=VHc;>^knR8fr8>s&woPzz7z%V`#SK{OYu;7srxj9FGFuQ1AtaHo@N z&H5A^nqaME*7lP z+@40KXmSfoppVPMJ%)%J>cG(fr`K5B5ESw~7p(vI=|@>tBm4q2S}$#oWXVn51Ds_& zL;?s5nYF(O~6iK;VY)7j&w=5Cfvj#pSwaA;$EwoU% za}eAPGAlGAPoP-i@7Bu{B{vb+Ax8e4+LHE-qk3N7?5s(l8A&I!RJE*<&RM7L{ve#N! zD3J_X$?Wn<$)>aRT4Zk;Lqm0vp1yi?rN+*x=Ne&T7JyJYbI6t!_0<}3eYJ#@sgx%N zyG~=!nIB&!czbB>t096?kYFofNZ&!Na@*=8GWKfjC z;AQ9fGlE@12IKEdRt>_E$8o_L&h()}+EVTZHNP&U%A9?Vl*l%2J2w9~kUn@Ou?%Qq zGN5GA!3UIF1sfqWi?-C5JbR4uUP27T@!jy`6Nw<`Sig+d0Y8;2H`oVz)2kTh2hDGN z7D{HLa1y=CG0>!pr&i(l?KV&Fj>OPG$}|@r+|<@c?A&4;T9hib)b;^{G|iJRF12rm zFqSc(kTJuQH}9zHI?_CFC2)1s>_`&>Qb%)|(AY5Hy!)Aiyr*94yHh8UFdh!;=L>!D z!+i`HSa`C0p=8_KHo^?L>lyGU8xTmcA(Fbs6J_E7r47?~D#wa0&g`!)#O`-74&5Op zPFa{)7VXA$GFtTyQ;)*_R7A!-s>MmJ67j)fK5Rfq*C-~3)*y(TTqOVdX#GKbEl>Cj zX0(6<0O0(eUHZ?lnyjj;jID~{%LchdqKk#aSA=p=QAXG(!e9lRlv1LQ_frk2w$ZAC zq;zb*9-AGT_URLn{-LC$c{(VaDe&nN@hkW>OZFLVnj`&!^Mv>6t#jthar))%s^tw} zn9%5SD4Ig36;qg%Rr z&REVjMYU2QEtCqc{8rkH6{`K# zd97(_sDHTOXf1i|&G{D;6XytRsr7Q?q*^;sWrc(F8S6}A$QQ7Ml%U6YqpZD{AOmN8 zH9?ZAl#n2g`MObYcNgVmK%J#x!YHTcfEK%sa&Dl5%I;{+c( z=+;>LX39-qd^1c()?@JE`L#|V#TJik!y4t*b0n(q#)(r=;**Y%vLxe+zRIk7!l~SY zzs(iq>DeL-Y@ug^4YuA^vD}WPlWcJQA%ZZL7`>>lzeW?i--ya6EHnn%BWhT9 zhC>2=iQ}QuN+-cmUt#8?d26Dm&bZis#-6}>QJ>`$_AvZOkUM0=_pA)&A&aM6maQpa z-b&eBT<(gPwa!s|x06aGU9d(iwM%x{xVeu|tb)=`m@e}u*rV)LyNM35k2jRBd|fLR zcN7R9zeo=`!BD!MK7-k+%2boY17^+5oS~8ZjRj+4MG=sm`TR8^P)~KEfcxUv+C~~d zqy5Eca8NbP7gC-_&N*N0eNZY=B)ecn{*-mvGN5Q$2HD?$v ziuf8ZIyB$8gf;mZZ~Fw}XqthQq*oi{1vq7!U{>H-kX;DYbUW zpP%?0YAzRO{FmS7?Gqi08;|^-uYhUZGVJhCx&prg>kNa>TIBdM4$9FasZkHAQi#9R?mYoLa9l{gLd=m3^j6!DFI@ zhc=;V;1oFJ)!AGX<(XURCH3l96bR^+TQN+2eEENxd+VsGw)OFwmXhuckq+tZ?(Qz> z?vzexX=DSE(%s$NDV-wHB@(~w^`65Kz32V@`K~e6hP}s{pEc)NGoSf{ku#Rry4dl8 z0ddG3mAnz+GVUOqhmY7=jhgCtou*-(%JII?m3NYhg@kWwJB4K*)r?OJ74HtoM zV3$%2JbxVsC4S1Yewj0JUD(d?LP0?ZLCLy6xwt?fh(e8?Poreso}W*Xh(fVE&n)h) z6uwLEZg#p`i_0EvcCwVoyv=sH+el}Qr|}Nuv=oIZUAvvk-cqg`PFEI%GLQ?c*D`dF z@KMMm=!X`Cf(c|*b{B;L4J5ml&?p=rvx2S2 zKh19A2iEQyfF(czj(bl^@l#R#mr_{2?SS>?M+$zRoT~OG@qbUW9|y7A9E^m9-{`zF zJHo7>URR*@WgPrme!@ZoV#MHnJuT&-jjxmq1htxmnT8q0gmj#4<)}ibgW%C>U+_45prO~ptHZq*4GH06gHcW{NxEAK z!LlE{pBy&s!^JxBaf$R83W2b}^1?q5{%&L>~{?*&Xq zMh3kc*9Sv1)(m$3(wHV)cRD(|lKvwXHAXzRCJ9*rapl=khg+~C6KF1C#je+-7NB`(# zBEbLkrqJ00Y|%8X29JpEymOwdR@P_8OTlrc7JDiRLh>wt#obk2Zx|pW@;2nd6z;rF zy6T~}yS*dEUio%i*XQR9)0(>tqVj5F2Pv(&D&(*c0H{-j~bWcwslfmjGH}ed%Y=kIAl4 zykJ@(^!(evetgn*B&|suTdq+bqhG^%LAHXmLA8SE`IWV+0e?CQXoJ1C7;H1u38WX4 zDga(Bl$3>juCi`>ZMkN7%(63=thb5x{2u2T$Lo13lwLp?D(}PD#d#**HNvko#NHTj zhZShT@%<$T2>u^x$SvTL2i2 zC1#oaqqGGhmm1z{p{muB)~lAytCg!(xv~3`Avxp;)%)}$8^D}JVVci+mYzr9N(UhqK9}&krJB5I4@m5L*s&p*tXsA?#mgUZ((8@o5VQ&ys%*Rp z%aL}Nt(M{2HVG06$&WlHVdl%~FU zCfXGnB@UowA7A=HxDS8s`aMK6B*Y)ZQd&x=t3zC$VUG|U)$e|#oQb7Fd5#YcJ_^b+ z-A^Jf82moVP9NaQj0PRTNut9b43F2@m9Z{P#E5qi&8fu>=ajd-;NvUSpQ*Psyb>DJ zb+^7=O6SY|i3tzy`S~-F(PQ8r_oJ_ zBGp6!E<`F_B0$QsVAD%pyE;!WwLwX1T^qt=l$Wc8Ed_@YAFtUc_EuBW1E*D6vD*6m zKqI3xzeb~nP95LIQFvtHMf=)#JHVF$%;w{$0MwZ-_>c=clxP!_V8D1W6B4|zsAzzn zU7-2Nx&yTKx;||mLj)@gR8wk;hhp6&lL{*fp?=XmW3OL(kN{8N#cLEJP0sA79u`n* z(xP6(0&|`7ssI&LX||5lT%W?x8S{~8ntEK(6o$z#5SOW$dY=f3&!TbWj)F`w&Wb`Y z>P7FFpH0RabWp^I_jF-|W+U{;@^F;qcI2D429GdEm~;v=E5G%F2TM6qMJQ6Gp@GoY zD;`t#XvPOes-VI~=cK0Vy%SZ_W*M=u&hG1f(RcT`I<}T_1cHK|DjWWFc14V7Pb7B= zeit3i!$d(M%D!P~#eCFMVLB~6Wuj%-l#GQc7nt3cPptS$WuDTvaNYnGk_GEHii4y3`ZJfUiKE(|b0d%2lY*a6B(h2Qwn&@6stR(pfMUF-bBdiD?gDHYv5G zg5-DK8JdYSal#HufZn8IAqc@@u27TQibnKNUQ zO_QEevbVNZaU( z%_WB`6`Pip^7Ha4xnQY96ugqHUr{6LarxRp0%gUq5?ZgS`&g9W?5kw*;?Kzvq<7h4 zQfkxH%2+`t;YBu5Ord%#;rOhOiT3IhGxG0-!QUT@JQVJ7L*u~uKmeoI;yQ1F4nJu)*(r8* zy#K03*+cORijk3Uof47ooqKAm`lXm5PC8mNu96^Vf_M0Q^E`LW=)5PDC7luWXR;`!pCLPgi|IbamvjoUy+)B zs5xQXmNb$KAY)sI3nd{oUx-7y=GU<@CxNpX4r9;l+Z;@YZIw`vDVHMWXD`_*hSobk zAyVDUv)6r*M&>S(Yw8JNa!YVz@5s*gMvT=#1++gRu<`JJ^<7 zD$mHNZTJYozNPqFeSdh*zNx?rZFFEx+}&*8mO#|QQmHo&|9U~N-jCW!1J(txO>XDv z44-jzfQ<=~?ggNVE9#}w6y4ZhFq3R=)trS0XiS+|Y^e6JZ0_?&GBi>g$l^Xnq=7=q zMUxy9=lx!jLlS29cvhcW3(j}Ll~vki+_#`9M;lEhHxL>GtztWfr!9=Wp+^_TAtv{s zc6(w$LME;{>+8`2xAv{{`TX|V!PnI@N-+Tk#^Jc}-V7;&!i(Y**>4iQVXe|nIiBO^ z$+%e5g{))k&VG1N`A&&dwdx)f>SBSMzavLtyWH-ET%J|W)vt8u72!xljd%V_5$<+u zmq5dO71^FGD8!A`PINJ=Z*TaIJb+t^~Y&(c09(+j_ zlE9j)#^*0g4CP8~Ih$sAIUkB<5^afu!i;wOiou1H-Q(IIg|8=C8<0JEc&Cr;kW;O$ zl)^U>J$POK-8MG24Fv4>m-#2J9)b(`M3vfhaZfCnj-{pK~C>pushRMps$zY$jn*ef})I@}`-PBaT8xf$O<5@wo#l5#i72BkN(I(0=8nVrx z$UygUwB-wp;JV?gqUmk19MQ_H_XygY*cBPucW4DYZjxcc8&pdBsB*)|%STMg3SLtpUT$?RsTPuEsOHOb3^^TC zwthbL5*fr&u8h>SP*-;_3aH6)nPBP*CpkmobE2W;0_}oRm(B5)fZs+Kh~qM0h>^6^3Te06(&HR`@e=ML-UDC=tH`;pMYUFkjk z-sd?YpLlw!WDMB8iPVP-_Wp>iISbw&SLB-kn7@Kl^EAvof?g&|43Ypzu|Z zWbZcw5ZmMvP4I1ztNV%MK=nQ;!pbn}WBh!o$vuN=qra^4F~b@wRMWupEX|`&-iE0Oir)!?n*DInd z$Hw5?djZeXx*RdP93f56Q!BpKC^mm$|9*m8jEb_#D(37>>-b`9!asmT9?yEO+%DMA zo3^T&VxCj1)u_5NfO2(%LvurmiudjHusm&OQcZeFR*S!jNcn}iqNX=BzB%s4o;VeM zkD6=kK)E68in+=ur-4v8zf3wF$4|AN8T)7TA1>R!Z_K?xcvt&g45*uU^rqRhQ^!>x zze&IokrvcHQ6T3X{{$70IA|~{m_SMUI0li~Gx;jS(9a;Ijr?=q#pi+#MCWZ#>1827 z*>42k`NiAgp_*g;`DoO?sNOIw`FuB6pwf(7*|p{>t)UJ30>Uzp8GkIoz&9IFK|hZ9 zDlt3goSnyS_M{jQkAU2&J>vttiFYggmov^KMN9pZ8puza`kwGoZ6E-ziS1F&8@SY( zGGk^0w8{8>abdTOV;eK#d1T@nI#0B9TUED)P-4SQejsF6@m=bFY+kJ5M zT3UCLPrqfH-NT>m%g0Yz4Yn6Bi}%x{?W8_xwrRQeeMk(RwqbO7fhTQ*)mMh;I^HRe~1MRKdE)nFIzRe1OBUieW;!XJe9h*}2jd8BPeInDXF zWzLz?&+GUH+4MtSBm1sGxU>m;*&_r@*8y(pDt1JQ?-B4jj|^^jb)@KtHhlGQ4Y}fC z16A~d=!!w|1+XN9EuUX>DPIXJ3+>&pc;l~HzPhRZ>Q&}O2iX$3t?7wB(QdvU7_Q?T zQRta?+cEpvtOI60C1+(Eex@dyLrN`}H6#0t-L(UADGI{T7uyA|aDQ|syPb<9fb;lV zG(d0Z6rwf}iIWFy@WzXL5@SLN%NsKM0Sj;stdDV`!( z|C}96*3FWzvfbcA!x~KEC6;wc&KE)2w4J>hqv}yM%{!GwgAXXYN-3SBDt1%r$9QkU zAdq|VFT$F$FFA&_#5YQm$Cmp0k|rcqWVzhQzpm@J_gxpH+-KZ(Vb~Ggk!x-+&-+c^ zp+F3C^k#W*$Xz(B(3J5n5EAdzlY&~z5 zDm;?N#H)scO}!a-vx0n!M&WFtc~n%8Lup!NF^b+c5s=4^W4Ox*m+BJN;hEfK#lkOw>kcHMe01ZhAZ#jYxEmP(2^@en+?xKFGlZ{e`+Cya78>S7$O~hj{ zj`x9+7_iq4NmX8}uA@9xW2>2Mpxj;5kod_76H$e%+=9be8;mG!#p9Axgn2CF#g2 z)qYf)#p)gn&4Y-u8Q~dPsGTRm7c7Cr__)nmsZla6CVhp_0cB}EX{E=~p4e246FJ$a zIw(6T@P^_d5JD&z%FXTB;YnBqSPf~J2HCpOep+O7gNv2AtZyw%u2zWR3_Qkm`p9hR zxiNXhWj>O)ofq(j=V(lgDj4Oc>A#>U6esCnup1g;beajPtqOq{VQ|xG!xsHQSEXLL z$KiaX_?=PWSXW_XVc6=!oJ$=O{O0F}m-1hlEV@)k_ib&a-dk!i@A)U;2~eT3NgD*1 z)DfkV+BV0Zct=hm9smd>&Ng_6GBlBoSK?_}oYHbL+-JKv<|*;P>cu_^ash}#vCk(Y zTA&5_pv;n5unWM36+Y}LjUtKP@=Jk`vn0eEEtv7kGemyp72IMC)Mx5k!bK*~{}7IJ z!owefBztT4rQxXXIHbmbPpzSMbo}OdRvENc$J#7?;A)1@rR|`qoYn@{v{D;5V};~B z@4;1f7e1-5T#Yn%p5c3(W_}$B#cmQxt?a~nua-NsRap&%$~WA^oNvGF_uk#Sd|JJ| zqe3ndfwJ%)8(!$Y$i#sb0e>lcs6+_-Q1}peFk`7vJwVp3XGLokP_a|ccJr_9T|_|< zFA8y72jxy+?|FBYf_=Z+cD4HrEQS;v2@Hmqqp7LvB8w#zDXGbDsxX9v=Bk#JuDX`e z+1C-%k0WH1jWPL4ZTu(gT?nMfoSH>U(I5agF|M0=g7sG6zSA-)hOc_9p;_Q+q=r7G zK__2l!cipKI>K|hh_+w*T8hqNry^iqdkk!=M86dTSQsHITSpTE!@t~|IV&vv(C6UM z0`zD?o8h=*4_XkA#ZaO%Z(MPmLj!R&91(CC!XOcniWe$lgTeUtr$I^m=D_ zp(-VMtj-k#a?7ckp-iZ!6UiikGqJqbK%}cEm?gOXxv#$XJFVFa6VAjX=8yQRE@|K4 z_x-*uW(Wljd^)-`eM_u)PRAjXZPnE5uvF^!&{@Q#-S7TVTn>#4J%Ak+zDs@3cp!i)I$qYz+F~P-t&*7OqJX{UL9AI;n)Vbvd zsSU<`P4TYoeghXAONg%NH@38b-^W-Yn_Fjbo*#(3Xbln0Ach_=j!Ybs{odCvZq-9{ zD>lX-nNutbE&;g*251rhu0)IrGZ`=(NR?xYhpIEe6Xxf34-=~<>>1ve&P&R-t2hGf zo~5aHp5KH+W*ELSb2|&CE+_B~wa>wW(%8#W;T361s1=6!w@MCsO zBylHC^m&rncrGb{Dt^MxK|oNSzUTiM`l(q0y_;hB)Yg3<&L!jb(=-4LVtyA$b>)ex zS^R-=5-w3|-i8%({vdc+MhimZxRkGRg5C=~*<8SMD~)|)j~xH5c$08^!ZvDbA^4?? zkO1dYmFFnXhyTiz`|8=@PLprj4Guph?}Y)ltlM!grV;P-nlb6@X2P@l7WPdca9O$z zjTc_p(r$L6A!PF1gY{U-uU_;3!iZ-=nHhNzG!REU?K(oKzdj6Z0aLfoVsZ&>LajY|fc)@d*NXdeT zR0)q6ydfOT<`~?$;&lx^@K;JI=L$W8;qLYoqXWj)20A1}>CzL=|r(^!&A7!4Ec*p`^V3aYLggm%ClNi$aawIj}GpBfB&)@ zJe$g@L(LEtf;u$Nk+)_z+ue`6CJPwryEI04WhzC+7* zvCf6#7_r+S+GC5*7^O~(o7yWZrV2LTJgt9;3#o(2A96eI zUmoZTHy(`FGcgE&l0;Tp3*07_iAP^%-HzPcx)LUdkB>J{D=J5l+2*M2!Y0qRMcc6n zIa#CSbI~W)>fA&OwL7-vyWD(3_$=t-^8CISVz$rudgwE?JGWG=aj?3iZ0R`Z0|l4!=ku@ z8$@)NI=b^{S9lA}2SndqV}+>qpNmuOA$*a3gSCSZZ$lxd*C4F}niD46D3_cLnstPl z%Ys@fm7(7y6WO%oFu3L0;lS7|XMu267gpCS*&v}VglSwMSFgr!;9q1>n?bc_uzpX@ zjdsRJmX!;^#O|Z3v@npmOI{@<*r${f7-&6)Lb}Qq&6P64Who+d=zw^;IGG*8rx+_T zLH>PH%&?^^z}~(Xvucjt6bwr$bDMmYrnu=x{(u3%V#|Kpu*bYYn){-eyCN3xa6Bs44#n&BH&% zGaU(O@mk*X0b_sJy-U5F3A`*hg!3r70)s4$)kh9ANx}hA>V%aqveb~abmGewI`c0y z3@-MNhs!o>**wiE2v2R*IXq+<0i57LA(T-TaH$c%{1q3-Fa4R6vFycD2Txu30S zL%!!JhUOb(4}$6-?{mkLVWBvwjYF;%8D4^SOf?u~7;Z9Ygo@xzV5+jmNKtwSaW@|U z^CLt$(_-X%adu^V?-z8si8|l33v0}`(6gFK<>fj-)8G^*g}LUkMjm$`iE-Nrd;3oQ z;Cm0h3q9-%Nki(0Ag!~EFX{AA;0<`laQ-t*o}&6lGP-uTBw&bd^6W*8o(vDBVASTq$MrS=q@pg?ys6D1N{In^O0| zyAe?(7rboZ&Y!rLJ^_tqQexxYoemg!O*cE_V@{DluQUZH%29Q*qN$NpW^%;4sCT zCTplcrwGQnVHAe%IGD0>%d*Ay&)nnf8aXBJakL&FV3ykY zDm7<(6j^-OmEEw_yIvJFqgr@;Eez`^<(@ch%G{o%RczuFgq<~GKE>D)f*SJ}AyP_d#Dh4TAbzpAc#EABR1 zQWOgN`9GDa;lrV|BH$P=DkvZtF%4RM6;&-9U~oynNx@OUp&2R|7#Wz#85ls! zRiRh`(4^p8;^}_iPbyMje%jXmF6}SN#{WZUSG%(hrb+()TiRnS-DU(}lR*Yfynj_T z`_XKEvn!4nlkEDTMqOfU__5}p_!=TrS3A((pG3SnJ~!me6^o5fXKLU`-rh&|W6&G< zo&_SPMS|`YFQ%&1TT>UcFJtr|N$mFa2ZqDtNlC%$pFAbI7466-L!XKEnLAbKq;SXc z#clB|t!G*#Vd2rPR^}RG9qkk8QY$N(ZFsQaA}^1fMt|!ZQzTs-k=}v_KMK)FRkx=? zuE+0Ippq^O^R99C{hl=U2{~75-u>c3R^6+9I_Ix<5jI*}0;>D+yuIPG2O|Wx-_>Gq z3+an9*O{k~>8A}-k4d84y~oDO!p@R}gL@71&Dsg_B_4ua@-O-;6-L}iF6}9Myb(He z9awHh-sU;?Jp&c#qQde1+s*9n0nQMCE#>b3XVwNTCN_W1gMYsm`}d210l4U%2H<)c zM2+;{lKm$!tpV^AJ~rjG5)aXIjj;s^pf0@$FwEBvU${>)`FP()fSs*1otw2)kE*rh z5(Cn`?w&7$0Rz^&WK$=gJ~)9jCf3OW#z;@)+Xh7}8R>w;(4KE5%}|tHTr2!w>$mrw zPOgn?9K#>f!6#OX2J0JfimONIkR?wr+}#%G)LxW}Rj1xK0Ch0l?5znK(cphN%;r~1Mf-w>CNM$ zia&zQ6y?U?#*2prnr|S@7)HDxMpcXslCFqi42F}VE$*3(%X74rtcywJukMz{9>TA7 z1|xtOGLoltiWbvr1t1&2$#bf2(jqA2vX8dam4KI}7f$4TLBbzR`rKC(NvNwnb^oBN zL^BX>bT=#VW-E@3dE(@k^YQq|>sj<3pbds#x;iacmCKpOMUIl-dq& zA=7S6F{LikiQaG7M4IL_4P3jlLYr>3z**)jyWZj8TXHSU@t=YzFE`k`vAN-hA(|-G z*dHyKNN&DBIiT4!B{>(yDV2v|`HVjOr`8Qp(0gez)P^V!z25@kY8yl1v~UiWU)S5-GX z|8qPWJye{uNZgy|ydB7Q1UnT0uhng+<{^}m_mkQ9*Z>_7h0Qo-G^peKWY5ukrDh4U zO)sAnIDWRNw$(jG2Nbn|D)XG>lZ&dA2JZc%y%hmt@5viqt(gPcnKkfy^?z?~b`G|{ zaC%PWCXPxjawAf-k~9)B3gSh|$1*fhBb(cb2m=(5l@Vx>!)TH4CDLz8;Azp)b%cOl z;APw!1{EX1na<9vo?i@Kz`u2Cn2b1;g5y@QI*XXR2zQ*6u5@caiI8NRLW!4pldX@u z-0d%|O9vbTLBY_#f!zY|c-qy#{O8vnA4niy1%CYfyWen@IT4_b7K$SIQ27l;bT^R#EkrT#m~>51^Ds*kF2KuH^pCl>0`P-g){s~ z_>r#rmvnyxApEzS{s?dQnEw2i^gn|g{&nv^hdcZ$^#?y9;7I%!fbp+;{}%l4F*AP- z*7OLh0=~X~1^%Kr`;_}MM$ID^4JgC;U)(4DNS;EU1~+(wG61DQzeE2R>fkB#sh{^p zs0VQS=6C4Nsod{g-=A`yIxc?Xf&%TA{=ogC8{?)Sd8D!fEr0)*$*1$srx|_f+2j!!0PJ;t zM?SSB{waBUN_}dR{75C_{J&8D^(j0x+I)m-@%#z?PZQ3k8GULc`N(4B{hzG=Pn*f7 zcX?{K_jnf={{MBCKR?)~b~=wFW8r_2{$#cDG*?fpWgb~nBEPVHS-Jf=Ur&ut9$AFI zLFTa<{nI4nX?~uXDLgU+#D8b}YZZCA0sV-o)chyvXUYCtq^BF8j|>;>-x>d!ou`{k zk0=$rUr>)*Pfs7t(`}wd1cClj#P6FvPiap#6CP<$hEHgJemGBMMvnk}vws5qRf6<1 zS5KuSkI*LIOz^Q1{e=Dx*~wGrQ=!Bow9w)y^p}eMPZ7ma=+m|TBQ(kKH|YNy(SM&V z{#gC~Jdi*h9@ee?G4K0pE&SJH==a1wR>O~p5r7ltzbB1969b>%-)rOljrp-Ue#D6U z8|HDT`_BjTr`7Ub$Pa$O|2N=&*Utas{%KY4$i1}tm-0Sl^auBGjUX=t3EWcqA)6xy OX#z%AD7F9b?f(HL1~IMx literal 0 HcmV?d00001 diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidCompiledStatement.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidCompiledStatement.java new file mode 100644 index 0000000..0c6a060 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidCompiledStatement.java @@ -0,0 +1,245 @@ +package com.j256.ormlite.android; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; + +import com.j256.ormlite.android.compat.ApiCompatibility; +import com.j256.ormlite.android.compat.ApiCompatibility.CancellationHook; +import com.j256.ormlite.android.compat.ApiCompatibilityUtils; +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Android implementation of the compiled statement. + * + * @author kevingalligan, graywatson + */ +public class AndroidCompiledStatement implements CompiledStatement { + + private static Logger logger = LoggerFactory.getLogger(AndroidCompiledStatement.class); + + private static final String[] NO_STRING_ARGS = new String[0]; + private static final ApiCompatibility apiCompatibility = ApiCompatibilityUtils.getCompatibility(); + + private final String sql; + private final SQLiteDatabase db; + private final StatementType type; + private final boolean cancelQueriesEnabled; + + private Cursor cursor; + private List args; + private Integer max; + private CancellationHook cancellationHook; + + public AndroidCompiledStatement(String sql, SQLiteDatabase db, StatementType type, boolean cancelQueriesEnabled) { + this.sql = sql; + this.db = db; + this.type = type; + this.cancelQueriesEnabled = cancelQueriesEnabled; + } + + public int getColumnCount() throws SQLException { + return getCursor().getColumnCount(); + } + + public String getColumnName(int column) throws SQLException { + return getCursor().getColumnName(column); + } + + public DatabaseResults runQuery(ObjectCache objectCache) throws SQLException { + // this could come from DELETE or UPDATE, just not a SELECT + if (!type.isOkForQuery()) { + throw new IllegalArgumentException("Cannot call query on a " + type + " statement"); + } + return new AndroidDatabaseResults(getCursor(), objectCache); + } + + public int runUpdate() throws SQLException { + if (!type.isOkForUpdate()) { + throw new IllegalArgumentException("Cannot call update on a " + type + " statement"); + } + String finalSql; + if (max == null) { + finalSql = sql; + } else { + finalSql = sql + " " + max; + } + return execSql(db, "runUpdate", finalSql, getArgArray()); + } + + public int runExecute() throws SQLException { + if (!type.isOkForExecute()) { + throw new IllegalArgumentException("Cannot call execute on a " + type + " statement"); + } + return execSql(db, "runExecute", sql, getArgArray()); + } + + public void close() throws IOException { + if (cursor != null && !cursor.isClosed()) { + try { + cursor.close(); + } catch (android.database.SQLException e) { + throw new IOException("Problems closing Android cursor", e); + } + } + cancellationHook = null; + } + + public void closeQuietly() { + if (cursor != null) { + cursor.close(); + } + } + + public void cancel() { + if (cancellationHook != null) { + cancellationHook.cancel(); + } + } + + public void setObject(int parameterIndex, Object obj, SqlType sqlType) throws SQLException { + isInPrep(); + if (args == null) { + args = new ArrayList(); + } + if (obj == null) { + args.add(parameterIndex, null); + return; + } + + switch (sqlType) { + case STRING : + case LONG_STRING : + case DATE : + case BOOLEAN : + case CHAR : + case BYTE : + case SHORT : + case INTEGER : + case LONG : + case FLOAT : + case DOUBLE : + args.add(parameterIndex, obj.toString()); + break; + case BYTE_ARRAY : + case SERIALIZABLE : + args.add(parameterIndex, obj); + break; + case BLOB : + // this is only for derby serializable + case BIG_DECIMAL : + // this should be handled as a STRING + throw new SQLException("Invalid Android type: " + sqlType); + case UNKNOWN : + default : + throw new SQLException("Unknown sql argument type: " + sqlType); + } + } + + public void setMaxRows(int max) throws SQLException { + isInPrep(); + this.max = max; + } + + public void setQueryTimeout(long millis) { + // as far as I could tell this is not supported by Android API + } + + /*** + * This is mostly an internal class but is exposed for those people who need access to the Cursor itself. + * + *

+ * NOTE: This is not thread safe. Not sure if we need it, but keep that in mind. + *

+ */ + public Cursor getCursor() throws SQLException { + if (cursor == null) { + String finalSql = null; + try { + if (max == null) { + finalSql = sql; + } else { + finalSql = sql + " " + max; + } + if (cancelQueriesEnabled) { + cancellationHook = apiCompatibility.createCancellationHook(); + } + cursor = apiCompatibility.rawQuery(db, finalSql, getStringArray(), cancellationHook); + cursor.moveToFirst(); + logger.trace("{}: started rawQuery cursor for: {}", this, finalSql); + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("Problems executing Android query: " + finalSql, e); + } + } + + return cursor; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); + } + + /** + * Execute some SQL on the database and return the number of rows changed. + */ + static int execSql(SQLiteDatabase db, String label, String finalSql, Object[] argArray) throws SQLException { + try { + db.execSQL(finalSql, argArray); + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("Problems executing " + label + " Android statement: " + finalSql, e); + } + int result; + SQLiteStatement stmt = null; + try { + // ask sqlite how many rows were just changed + stmt = db.compileStatement("SELECT CHANGES()"); + result = (int) stmt.simpleQueryForLong(); + } catch (android.database.SQLException e) { + // ignore the exception and just return 1 if it failed + result = 1; + } finally { + if (stmt != null) { + stmt.close(); + } + } + logger.trace("executing statement {} changed {} rows: {}", label, result, finalSql); + return result; + } + + private void isInPrep() throws SQLException { + if (cursor != null) { + throw new SQLException("Query already run. Cannot add argument values."); + } + } + + private Object[] getArgArray() { + if (args == null) { + // this will work for Object[] as well as String[] + return NO_STRING_ARGS; + } else { + return args.toArray(new Object[args.size()]); + } + } + + private String[] getStringArray() { + if (args == null) { + return NO_STRING_ARGS; + } else { + // we assume we have Strings in args + return args.toArray(new String[args.size()]); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidConnectionSource.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidConnectionSource.java new file mode 100644 index 0000000..25ebb9a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidConnectionSource.java @@ -0,0 +1,144 @@ +package com.j256.ormlite.android; + +import java.sql.SQLException; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.db.SqliteAndroidDatabaseType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.BaseConnectionSource; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseConnectionProxyFactory; + +/** + * Android version of the connection source. Takes a standard Android {@link SQLiteOpenHelper}. For best results, use + * {@link OrmLiteSqliteOpenHelper}. You can also construct with a {@link SQLiteDatabase}. + * + * @author kevingalligan, graywatson + */ +public class AndroidConnectionSource extends BaseConnectionSource implements ConnectionSource { + + private static final Logger logger = LoggerFactory.getLogger(AndroidConnectionSource.class); + + private final SQLiteOpenHelper helper; + private final SQLiteDatabase sqliteDatabase; + private DatabaseConnection connection = null; + private volatile boolean isOpen = true; + private final DatabaseType databaseType = new SqliteAndroidDatabaseType(); + private static DatabaseConnectionProxyFactory connectionProxyFactory; + private boolean cancelQueriesEnabled = false; + + public AndroidConnectionSource(SQLiteOpenHelper helper) { + this.helper = helper; + this.sqliteDatabase = null; + } + + public AndroidConnectionSource(SQLiteDatabase sqliteDatabase) { + this.helper = null; + this.sqliteDatabase = sqliteDatabase; + } + + public DatabaseConnection getReadOnlyConnection() throws SQLException { + /* + * We have to use the read-write connection because getWritableDatabase() can call close on + * getReadableDatabase() in the future. This has something to do with Android's SQLite connection management. + * + * See android docs: http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html + */ + return getReadWriteConnection(); + } + + public DatabaseConnection getReadWriteConnection() throws SQLException { + DatabaseConnection conn = getSavedConnection(); + if (conn != null) { + return conn; + } + if (connection == null) { + SQLiteDatabase db; + if (sqliteDatabase == null) { + try { + db = helper.getWritableDatabase(); + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("Getting a writable database from helper " + helper + " failed", e); + } + } else { + db = sqliteDatabase; + } + connection = new AndroidDatabaseConnection(db, true, cancelQueriesEnabled); + if (connectionProxyFactory != null) { + connection = connectionProxyFactory.createProxy(connection); + } + logger.trace("created connection {} for db {}, helper {}", connection, db, helper); + } else { + logger.trace("{}: returning read-write connection {}, helper {}", this, connection, helper); + } + return connection; + } + + public void releaseConnection(DatabaseConnection connection) { + // noop since connection management is handled by AndroidOS + } + + public boolean saveSpecialConnection(DatabaseConnection connection) throws SQLException { + return saveSpecial(connection); + } + + public void clearSpecialConnection(DatabaseConnection connection) { + clearSpecial(connection, logger); + } + + public void close() { + // the helper is closed so it calls close here, so this CANNOT be a call back to helper.close() + isOpen = false; + } + + public void closeQuietly() { + close(); + } + + public DatabaseType getDatabaseType() { + return databaseType; + } + + public boolean isOpen() { + return isOpen; + } + + public boolean isSingleConnection() { + return true; + } + + /** + * Set to enable connection proxying. Set to null to disable. + */ + public static void setDatabaseConnectionProxyFactory(DatabaseConnectionProxyFactory connectionProxyFactory) { + AndroidConnectionSource.connectionProxyFactory = connectionProxyFactory; + } + + public boolean isCancelQueriesEnabled() { + return cancelQueriesEnabled; + } + + /** + * Set to true to enable the canceling of queries. + * + *

+ * NOTE: This will incur a slight memory increase for all Cursor based queries -- even if cancel is not + * called for them. + *

+ */ + public void setCancelQueriesEnabled(boolean cancelQueriesEnabled) { + this.cancelQueriesEnabled = cancelQueriesEnabled; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseConnection.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseConnection.java new file mode 100644 index 0000000..22cb3af --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseConnection.java @@ -0,0 +1,414 @@ +package com.j256.ormlite.android; + +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Savepoint; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.misc.VersionUtils; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.GeneratedKeyHolder; + +/** + * Database connection for Android. + * + * @author kevingalligan, graywatson + */ +public class AndroidDatabaseConnection implements DatabaseConnection { + + private static final String ANDROID_VERSION = "VERSION__4.49-SNAPSHOT__"; + + private static Logger logger = LoggerFactory.getLogger(AndroidDatabaseConnection.class); + private static final String[] NO_STRING_ARGS = new String[0]; + + private final SQLiteDatabase db; + private final boolean readWrite; + private final boolean cancelQueriesEnabled; + + static { + VersionUtils.checkCoreVersusAndroidVersions(ANDROID_VERSION); + } + + public AndroidDatabaseConnection(SQLiteDatabase db, boolean readWrite) { + this(db, readWrite, false); + } + + public AndroidDatabaseConnection(SQLiteDatabase db, boolean readWrite, boolean cancelQueriesEnabled) { + this.db = db; + this.readWrite = readWrite; + this.cancelQueriesEnabled = cancelQueriesEnabled; + logger.trace("{}: db {} opened, read-write = {}", this, db, readWrite); + } + + public boolean isAutoCommitSupported() { + return true; + } + + public boolean isAutoCommit() throws SQLException { + try { + boolean inTransaction = db.inTransaction(); + logger.trace("{}: in transaction is {}", this, inTransaction); + // You have to explicitly commit your transactions, so this is sort of correct + return !inTransaction; + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("problems getting auto-commit from database", e); + } + } + + public void setAutoCommit(boolean autoCommit) { + /* + * Sqlite does not support auto-commit. The various JDBC drivers seem to implement it with the use of a + * transaction. That's what we are doing here. + */ + if (autoCommit) { + if (db.inTransaction()) { + db.setTransactionSuccessful(); + db.endTransaction(); + } + } else { + if (!db.inTransaction()) { + db.beginTransaction(); + } + } + } + + public Savepoint setSavePoint(String name) throws SQLException { + try { + db.beginTransaction(); + logger.trace("{}: save-point set with name {}", this, name); + return new OurSavePoint(name); + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("problems beginning transaction " + name, e); + } + } + + /** + * Return whether this connection is read-write or not (real-only). + */ + public boolean isReadWrite() { + return readWrite; + } + + public void commit(Savepoint savepoint) throws SQLException { + try { + db.setTransactionSuccessful(); + db.endTransaction(); + if (savepoint == null) { + logger.trace("{}: transaction is successfuly ended", this); + } else { + logger.trace("{}: transaction {} is successfuly ended", this, savepoint.getSavepointName()); + } + } catch (android.database.SQLException e) { + if (savepoint == null) { + throw SqlExceptionUtil.create("problems commiting transaction", e); + } else { + throw SqlExceptionUtil.create("problems commiting transaction " + savepoint.getSavepointName(), e); + } + } + } + + public void rollback(Savepoint savepoint) throws SQLException { + try { + // no setTransactionSuccessful() means it is a rollback + db.endTransaction(); + if (savepoint == null) { + logger.trace("{}: transaction is ended, unsuccessfuly", this); + } else { + logger.trace("{}: transaction {} is ended, unsuccessfuly", this, savepoint.getSavepointName()); + } + } catch (android.database.SQLException e) { + if (savepoint == null) { + throw SqlExceptionUtil.create("problems rolling back transaction", e); + } else { + throw SqlExceptionUtil.create("problems rolling back transaction " + savepoint.getSavepointName(), e); + } + } + } + + public int executeStatement(String statementStr, int resultFlags) throws SQLException { + return AndroidCompiledStatement.execSql(db, statementStr, statementStr, NO_STRING_ARGS); + } + + public CompiledStatement compileStatement(String statement, StatementType type, FieldType[] argFieldTypes, + int resultFlags) { + // resultFlags argument is not used in Android-land since the {@link Cursor} is bi-directional. + CompiledStatement stmt = new AndroidCompiledStatement(statement, db, type, cancelQueriesEnabled); + logger.trace("{}: compiled statement got {}: {}", this, stmt, statement); + return stmt; + } + + public int insert(String statement, Object[] args, FieldType[] argFieldTypes, GeneratedKeyHolder keyHolder) + throws SQLException { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement(statement); + bindArgs(stmt, args, argFieldTypes); + long rowId = stmt.executeInsert(); + if (keyHolder != null) { + keyHolder.addKey(rowId); + } + /* + * I've decided to not do the CHANGES() statement here like we do down below in UPDATE because we know that + * it worked (since it didn't throw) so we know that 1 is right. + */ + int result = 1; + logger.trace("{}: insert statement is compiled and executed, changed {}: {}", this, result, statement); + return result; + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("inserting to database failed: " + statement, e); + } finally { + closeQuietly(stmt); + } + } + + public int update(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { + return update(statement, args, argFieldTypes, "updated"); + } + + public int delete(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { + // delete is the same as update + return update(statement, args, argFieldTypes, "deleted"); + } + + public Object queryForOne(String statement, Object[] args, FieldType[] argFieldTypes, + GenericRowMapper rowMapper, ObjectCache objectCache) throws SQLException { + Cursor cursor = null; + try { + cursor = db.rawQuery(statement, toStrings(args)); + AndroidDatabaseResults results = new AndroidDatabaseResults(cursor, objectCache); + logger.trace("{}: queried for one result: {}", this, statement); + if (!results.first()) { + return null; + } else { + T first = rowMapper.mapRow(results); + if (results.next()) { + return MORE_THAN_ONE; + } else { + return first; + } + } + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("queryForOne from database failed: " + statement, e); + } finally { + closeQuietly(cursor); + } + } + + public long queryForLong(String statement) throws SQLException { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement(statement); + long result = stmt.simpleQueryForLong(); + logger.trace("{}: query for long simple query returned {}: {}", this, result, statement); + return result; + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("queryForLong from database failed: " + statement, e); + } finally { + closeQuietly(stmt); + } + } + + public long queryForLong(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { + Cursor cursor = null; + AndroidDatabaseResults results = null; + try { + cursor = db.rawQuery(statement, toStrings(args)); + results = new AndroidDatabaseResults(cursor, null); + long result; + if (results.first()) { + result = results.getLong(0); + } else { + result = 0L; + } + logger.trace("{}: query for long raw query returned {}: {}", this, result, statement); + return result; + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("queryForLong from database failed: " + statement, e); + } finally { + closeQuietly(cursor); + IOUtils.closeQuietly(results); + } + } + + public void close() throws IOException { + try { + db.close(); + logger.trace("{}: db {} closed", this, db); + } catch (android.database.SQLException e) { + throw new IOException("problems closing the database connection", e); + } + } + + public void closeQuietly() { + IOUtils.closeQuietly(this); + } + + public boolean isClosed() throws SQLException { + try { + boolean isOpen = db.isOpen(); + logger.trace("{}: db {} isOpen returned {}", this, db, isOpen); + return !isOpen; + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("problems detecting if the database is closed", e); + } + } + + public boolean isTableExists(String tableName) { + Cursor cursor = + db.rawQuery("SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name = '" + tableName + "'", null); + try { + boolean result; + if (cursor.getCount() > 0) { + result = true; + } else { + result = false; + } + logger.trace("{}: isTableExists '{}' returned {}", this, tableName, result); + return result; + } finally { + cursor.close(); + } + } + + private int update(String statement, Object[] args, FieldType[] argFieldTypes, String label) throws SQLException { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement(statement); + bindArgs(stmt, args, argFieldTypes); + stmt.execute(); + } catch (android.database.SQLException e) { + throw SqlExceptionUtil.create("updating database failed: " + statement, e); + } finally { + closeQuietly(stmt); + stmt = null; + } + int result; + try { + stmt = db.compileStatement("SELECT CHANGES()"); + result = (int) stmt.simpleQueryForLong(); + } catch (android.database.SQLException e) { + // ignore the exception and just return 1 + result = 1; + } finally { + closeQuietly(stmt); + } + logger.trace("{} statement is compiled and executed, changed {}: {}", label, result, statement); + return result; + } + + private void bindArgs(SQLiteStatement stmt, Object[] args, FieldType[] argFieldTypes) throws SQLException { + if (args == null) { + return; + } + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + if (arg == null) { + stmt.bindNull(i + 1); + } else { + SqlType sqlType = argFieldTypes[i].getSqlType(); + switch (sqlType) { + case STRING : + case LONG_STRING : + case CHAR : + stmt.bindString(i + 1, arg.toString()); + break; + case BOOLEAN : + case BYTE : + case SHORT : + case INTEGER : + case LONG : + stmt.bindLong(i + 1, ((Number) arg).longValue()); + break; + case FLOAT : + case DOUBLE : + stmt.bindDouble(i + 1, ((Number) arg).doubleValue()); + break; + case BYTE_ARRAY : + case SERIALIZABLE : + stmt.bindBlob(i + 1, (byte[]) arg); + break; + case DATE : + // this is mapped to a STRING under Android + case BLOB : + // this is only for derby serializable + case BIG_DECIMAL : + // this should be handled as a STRING + throw new SQLException("Invalid Android type: " + sqlType); + case UNKNOWN : + default : + throw new SQLException("Unknown sql argument type: " + sqlType); + } + } + } + } + + private String[] toStrings(Object[] args) { + if (args == null || args.length == 0) { + return null; + } + String[] strings = new String[args.length]; + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + if (arg == null) { + strings[i] = null; + } else { + strings[i] = arg.toString(); + } + } + + return strings; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); + } + + /** + * We can't use IOUtils here because older versions didn't implement Closeable. + */ + private void closeQuietly(Cursor cursor) { + if (cursor != null) { + cursor.close(); + } + } + + /** + * We can't use IOUtils here because older versions didn't implement Closeable. + */ + private void closeQuietly(SQLiteStatement statement) { + if (statement != null) { + statement.close(); + } + } + + private static class OurSavePoint implements Savepoint { + + private String name; + + public OurSavePoint(String name) { + this.name = name; + } + + public int getSavepointId() { + return 0; + } + + public String getSavepointName() { + return name; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseResults.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseResults.java new file mode 100644 index 0000000..157cbbc --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidDatabaseResults.java @@ -0,0 +1,244 @@ +package com.j256.ormlite.android; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.math.BigDecimal; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import android.database.Cursor; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.db.SqliteAndroidDatabaseType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Android implementation of our results object. + * + * @author kevingalligan, graywatson + */ +public class AndroidDatabaseResults implements DatabaseResults { + + private static final int MIN_NUM_COLUMN_NAMES_MAP = 8; + + private final Cursor cursor; + private final String[] columnNames; + private final Map columnNameMap; + private final ObjectCache objectCache; + private static final DatabaseType databaseType = new SqliteAndroidDatabaseType(); + + public AndroidDatabaseResults(Cursor cursor, ObjectCache objectCache) { + this.cursor = cursor; + this.columnNames = cursor.getColumnNames(); + if (this.columnNames.length >= MIN_NUM_COLUMN_NAMES_MAP) { + this.columnNameMap = new HashMap(); + for (int i = 0; i < this.columnNames.length; i++) { + // NOTE: this is case sensitive + this.columnNameMap.put(this.columnNames[i], i); + } + } else { + columnNameMap = null; + } + this.objectCache = objectCache; + } + + /** + * Constructor that allows you to inject a cursor that has already been configured with first-call set to false. + * + * @deprecated The firstCall is no longer needed since the library now calls first() and next on its own. + */ + @Deprecated + public AndroidDatabaseResults(Cursor cursor, boolean firstCall, ObjectCache objectCache) { + this(cursor, objectCache); + } + + public int getColumnCount() { + return cursor.getColumnCount(); + } + + public String[] getColumnNames() { + int colN = getColumnCount(); + String[] columnNames = new String[colN]; + for (int colC = 0; colC < colN; colC++) { + columnNames[colC] = cursor.getColumnName(colC); + } + return columnNames; + } + + public boolean first() { + return cursor.moveToFirst(); + } + + public boolean next() { + return cursor.moveToNext(); + } + + public boolean last() { + return cursor.moveToLast(); + } + + public boolean previous() { + return cursor.moveToPrevious(); + } + + public boolean moveRelative(int offset) { + return cursor.move(offset); + } + + public boolean moveAbsolute(int position) { + return cursor.moveToPosition(position); + } + + /** + * Returns the count of results from the cursor. + */ + public int getCount() { + return cursor.getCount(); + } + + /** + * Returns the position of the cursor in the list of results. + */ + public int getPosition() { + return cursor.getPosition(); + } + + public int findColumn(String columnName) throws SQLException { + int index = lookupColumn(columnName); + if (index >= 0) { + return index; + } + + /* + * Hack here. It turns out that if we've asked for '*' then the field foo is in the cursor as foo. But if we ask + * for a particular field list with DISTINCT, which escapes the field names, they are in the cursor _with_ the + * escaping. Ugly!! + */ + StringBuilder sb = new StringBuilder(columnName.length() + 4); + databaseType.appendEscapedEntityName(sb, columnName); + index = lookupColumn(sb.toString()); + if (index >= 0) { + return index; + } else { + String[] columnNames = cursor.getColumnNames(); + throw new SQLException("Unknown field '" + columnName + "' from the Android sqlite cursor, not in:" + + Arrays.toString(columnNames)); + } + } + + public String getString(int columnIndex) { + return cursor.getString(columnIndex); + } + + public boolean getBoolean(int columnIndex) { + if (cursor.isNull(columnIndex) || cursor.getShort(columnIndex) == 0) { + return false; + } else { + return true; + } + } + + public char getChar(int columnIndex) throws SQLException { + String string = cursor.getString(columnIndex); + if (string == null || string.length() == 0) { + return 0; + } else if (string.length() == 1) { + return string.charAt(0); + } else { + throw new SQLException("More than 1 character stored in database column: " + columnIndex); + } + } + + public byte getByte(int columnIndex) { + return (byte) getShort(columnIndex); + } + + public byte[] getBytes(int columnIndex) { + return cursor.getBlob(columnIndex); + } + + public short getShort(int columnIndex) { + return cursor.getShort(columnIndex); + } + + public int getInt(int columnIndex) { + return cursor.getInt(columnIndex); + } + + public long getLong(int columnIndex) { + return cursor.getLong(columnIndex); + } + + public float getFloat(int columnIndex) { + return cursor.getFloat(columnIndex); + } + + public double getDouble(int columnIndex) { + return cursor.getDouble(columnIndex); + } + + public Timestamp getTimestamp(int columnIndex) throws SQLException { + throw new SQLException("Android does not support timestamp. Use JAVA_DATE_LONG or JAVA_DATE_STRING types"); + } + + public InputStream getBlobStream(int columnIndex) { + return new ByteArrayInputStream(cursor.getBlob(columnIndex)); + } + + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + throw new SQLException("Android does not support BigDecimal type. Use BIG_DECIMAL or BIG_DECIMAL_STRING types"); + } + + public boolean wasNull(int columnIndex) { + return cursor.isNull(columnIndex); + } + + public ObjectCache getObjectCache() { + return objectCache; + } + + public void close() { + cursor.close(); + } + + public void closeQuietly() { + close(); + } + + /*** + * Returns the underlying Android cursor object. This should not be used unless you know what you are doing. + */ + public Cursor getRawCursor() { + return cursor; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); + } + + private int lookupColumn(String columnName) { + // we either use linear search or our name map + if (columnNameMap == null) { + for (int i = 0; i < columnNames.length; i++) { + // NOTE: this is case sensitive + if (columnNames[i].equals(columnName)) { + return i; + } + } + return -1; + } else { + // NOTE: this is case sensitive + Integer index = columnNameMap.get(columnName); + if (index == null) { + return -1; + } else { + return index; + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidLog.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidLog.java new file mode 100644 index 0000000..2910e1a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/AndroidLog.java @@ -0,0 +1,168 @@ +package com.j256.ormlite.android; + +import android.util.Log; + +import com.j256.ormlite.logger.LoggerFactory; + +/** + * Implementation of our logger which delegates to the internal Android logger. + * + *

+ * To see log messages you will do something like: + *

+ * + *
+ * adb shell setprop log.tag.OrmLiteBaseActivity VERBOSE
+ * 
+ * + *

+ * NOTE: Unfortunately, Android variables are limited in size so this class takes that last 23 (sic) characters + * of the class name if it is larger than 23 characters. For example, if the class is AndroidDatabaseConnection you + * would do: + *

+ * + *
+ * adb shell setprop log.tag.droidDatabaseConnection VERBOSE
+ * 
+ * + *

+ * To see all ORMLite log messages use: + *

+ * + *
+ * adb shell setprop log.tag.ORMLite DEBUG
+ * 
+ * + * @author graywatson + */ +public class AndroidLog implements com.j256.ormlite.logger.Log { + + private final static String ALL_LOGS_NAME = "ORMLite"; + private final static int REFRESH_LEVEL_CACHE_EVERY = 200; + + private final static int MAX_TAG_LENGTH = 23; + private String className; + + // we do this because supposedly Log.isLoggable() always does IO + private volatile int levelCacheC = 0; + private final boolean levelCache[]; + + public AndroidLog(String className) { + // get the last part of the class name + this.className = LoggerFactory.getSimpleClassName(className); + // make sure that our tag length is not too long + int length = this.className.length(); + if (length > MAX_TAG_LENGTH) { + this.className = this.className.substring(length - MAX_TAG_LENGTH, length); + } + // find the maximum level value + int maxLevel = 0; + for (com.j256.ormlite.logger.Log.Level level : com.j256.ormlite.logger.Log.Level.values()) { + int androidLevel = levelToAndroidLevel(level); + if (androidLevel > maxLevel) { + maxLevel = androidLevel; + } + } + levelCache = new boolean[maxLevel + 1]; + refreshLevelCache(); + } + + public boolean isLevelEnabled(Level level) { + // we don't care if this is not synchronized, it will be updated sooner or later and multiple updates are fine. + if (++levelCacheC >= REFRESH_LEVEL_CACHE_EVERY) { + refreshLevelCache(); + levelCacheC = 0; + } + int androidLevel = levelToAndroidLevel(level); + if (androidLevel < levelCache.length) { + return levelCache[androidLevel]; + } else { + return isLevelEnabledInternal(androidLevel); + } + } + + public void log(Level level, String msg) { + switch (level) { + case TRACE : + Log.v(className, msg); + break; + case DEBUG : + Log.d(className, msg); + break; + case INFO : + Log.i(className, msg); + break; + case WARNING : + Log.w(className, msg); + break; + case ERROR : + Log.e(className, msg); + break; + case FATAL : + Log.e(className, msg); + break; + default : + Log.i(className, msg); + break; + } + } + + public void log(Level level, String msg, Throwable t) { + switch (level) { + case TRACE : + Log.v(className, msg, t); + break; + case DEBUG : + Log.d(className, msg, t); + break; + case INFO : + Log.i(className, msg, t); + break; + case WARNING : + Log.w(className, msg, t); + break; + case ERROR : + Log.e(className, msg, t); + break; + case FATAL : + Log.e(className, msg, t); + break; + default : + Log.i(className, msg, t); + break; + } + } + + private void refreshLevelCache() { + for (com.j256.ormlite.logger.Log.Level level : com.j256.ormlite.logger.Log.Level.values()) { + int androidLevel = levelToAndroidLevel(level); + if (androidLevel < levelCache.length) { + levelCache[androidLevel] = isLevelEnabledInternal(androidLevel); + } + } + } + + private boolean isLevelEnabledInternal(int androidLevel) { + // this is supposedly expensive with an IO operation for each call so we cache them into levelCache[] + return Log.isLoggable(className, androidLevel) || Log.isLoggable(ALL_LOGS_NAME, androidLevel); + } + + private int levelToAndroidLevel(com.j256.ormlite.logger.Log.Level level) { + switch (level) { + case TRACE : + return Log.VERBOSE; + case DEBUG : + return Log.DEBUG; + case INFO : + return Log.INFO; + case WARNING : + return Log.WARN; + case ERROR : + return Log.ERROR; + case FATAL : + return Log.ERROR; + default : + return Log.INFO; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/DatabaseTableConfigUtil.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/DatabaseTableConfigUtil.java new file mode 100644 index 0000000..2220b27 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/DatabaseTableConfigUtil.java @@ -0,0 +1,429 @@ +package com.j256.ormlite.android; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DataPersister; +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.DatabaseFieldConfig; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.DatabaseTableConfig; + +/** + * Class which uses reflection to make the job of processing the {@link DatabaseField} annotation more efficient. In + * current (as of 11/2011) versions of Android, Annotations are ghastly slow. This uses reflection on the Android + * classes to work around this issue. Gross and a hack but a significant (~20x) performance improvement. + * + *

+ * Thanks much go to Josh Guilfoyle for the idea and the code framework to make this happen. + *

+ * + * @author joshguilfoyle, graywatson + */ +public class DatabaseTableConfigUtil { + + /** + * Set this system property to any value to disable the annotations hack which seems to cause problems on certain + * operating systems. + */ + public static final String DISABLE_ANNOTATION_HACK_SYSTEM_PROPERTY = "ormlite.annotation.hack.disable"; + + private static Class annotationFactoryClazz; + private static Field elementsField; + private static Class annotationMemberClazz; + private static Field nameField; + private static Field valueField; + private static int workedC = 0; + + private static final int[] configFieldNums; + + static { + /* + * If we are dealing with older versions of the OS and if we've not disabled the annotation hack... + */ + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH + && System.getProperty(DISABLE_ANNOTATION_HACK_SYSTEM_PROPERTY) == null) { + configFieldNums = lookupClasses(); + } else { + configFieldNums = null; + } + } + + /** + * Build our list table config from a class using some annotation fu around. + */ + public static DatabaseTableConfig fromClass(ConnectionSource connectionSource, Class clazz) + throws SQLException { + DatabaseType databaseType = connectionSource.getDatabaseType(); + String tableName = DatabaseTableConfig.extractTableName(clazz); + List fieldConfigs = new ArrayList(); + for (Class classWalk = clazz; classWalk != null; classWalk = classWalk.getSuperclass()) { + for (Field field : classWalk.getDeclaredFields()) { + DatabaseFieldConfig config = configFromField(databaseType, tableName, field); + if (config != null && config.isPersisted()) { + fieldConfigs.add(config); + } + } + } + if (fieldConfigs.size() == 0) { + return null; + } else { + return new DatabaseTableConfig(clazz, tableName, fieldConfigs); + } + } + + /** + * Return the number of fields configured using our reflection hack. This is for testing. + */ + public static int getWorkedC() { + return workedC; + } + + /** + * This does all of the class reflection fu to find our classes, find the order of field names, and construct our + * array of ConfigField entries the correspond to the AnnotationMember array. + */ + private static int[] lookupClasses() { + Class annotationMemberArrayClazz; + try { + annotationFactoryClazz = Class.forName("org.apache.harmony.lang.annotation.AnnotationFactory"); + annotationMemberClazz = Class.forName("org.apache.harmony.lang.annotation.AnnotationMember"); + annotationMemberArrayClazz = Class.forName("[Lorg.apache.harmony.lang.annotation.AnnotationMember;"); + } catch (ClassNotFoundException e) { + return null; + } + + Field fieldField; + try { + elementsField = annotationFactoryClazz.getDeclaredField("elements"); + elementsField.setAccessible(true); + + nameField = annotationMemberClazz.getDeclaredField("name"); + nameField.setAccessible(true); + valueField = annotationMemberClazz.getDeclaredField("value"); + valueField.setAccessible(true); + + fieldField = DatabaseFieldSample.class.getDeclaredField("field"); + } catch (SecurityException e) { + return null; + } catch (NoSuchFieldException e) { + return null; + } + + DatabaseField databaseField = fieldField.getAnnotation(DatabaseField.class); + InvocationHandler proxy = Proxy.getInvocationHandler(databaseField); + if (proxy.getClass() != annotationFactoryClazz) { + return null; + } + + try { + // this should be an array of AnnotationMember objects + Object elements = elementsField.get(proxy); + if (elements == null || elements.getClass() != annotationMemberArrayClazz) { + return null; + } + + Object[] elementArray = (Object[]) elements; + int[] configNums = new int[elementArray.length]; + + // build our array of field-numbers that match the AnnotationMember array + for (int i = 0; i < elementArray.length; i++) { + String name = (String) nameField.get(elementArray[i]); + configNums[i] = configFieldNameToNum(name); + } + return configNums; + } catch (IllegalAccessException e) { + return null; + } + } + + /* + * NOTE: we are doing this instead of an enum (which otherwise would be much better) because we don't want to take + * the class-size hit that comes with each enum being its own class. + */ + private static final int COLUMN_NAME = 1; + private static final int DATA_TYPE = 2; + private static final int DEFAULT_VALUE = 3; + private static final int WIDTH = 4; + private static final int CAN_BE_NULL = 5; + private static final int ID = 6; + private static final int GENERATED_ID = 7; + private static final int GENERATED_ID_SEQUENCE = 8; + private static final int FOREIGN = 9; + private static final int USE_GET_SET = 10; + private static final int UNKNOWN_ENUM_NAME = 11; + private static final int THROW_IF_NULL = 12; + private static final int PERSISTED = 13; + private static final int FORMAT = 14; + private static final int UNIQUE = 15; + private static final int UNIQUE_COMBO = 16; + private static final int INDEX = 17; + private static final int UNIQUE_INDEX = 18; + private static final int INDEX_NAME = 19; + private static final int UNIQUE_INDEX_NAME = 20; + private static final int FOREIGN_AUTO_REFRESH = 21; + private static final int MAX_FOREIGN_AUTO_REFRESH_LEVEL = 22; + private static final int PERSISTER_CLASS = 23; + private static final int ALLOW_GENERATED_ID_INSERT = 24; + private static final int COLUMN_DEFINITON = 25; + private static final int FOREIGN_AUTO_CREATE = 26; + private static final int VERSION = 27; + private static final int FOREIGN_COLUMN_NAME = 28; + private static final int READ_ONLY = 29; + + /** + * Convert the name of the @DatabaseField fields into a number for easy processing later. + */ + private static int configFieldNameToNum(String configName) { + if (configName.equals("columnName")) { + return COLUMN_NAME; + } else if (configName.equals("dataType")) { + return DATA_TYPE; + } else if (configName.equals("defaultValue")) { + return DEFAULT_VALUE; + } else if (configName.equals("width")) { + return WIDTH; + } else if (configName.equals("canBeNull")) { + return CAN_BE_NULL; + } else if (configName.equals("id")) { + return ID; + } else if (configName.equals("generatedId")) { + return GENERATED_ID; + } else if (configName.equals("generatedIdSequence")) { + return GENERATED_ID_SEQUENCE; + } else if (configName.equals("foreign")) { + return FOREIGN; + } else if (configName.equals("useGetSet")) { + return USE_GET_SET; + } else if (configName.equals("unknownEnumName")) { + return UNKNOWN_ENUM_NAME; + } else if (configName.equals("throwIfNull")) { + return THROW_IF_NULL; + } else if (configName.equals("persisted")) { + return PERSISTED; + } else if (configName.equals("format")) { + return FORMAT; + } else if (configName.equals("unique")) { + return UNIQUE; + } else if (configName.equals("uniqueCombo")) { + return UNIQUE_COMBO; + } else if (configName.equals("index")) { + return INDEX; + } else if (configName.equals("uniqueIndex")) { + return UNIQUE_INDEX; + } else if (configName.equals("indexName")) { + return INDEX_NAME; + } else if (configName.equals("uniqueIndexName")) { + return UNIQUE_INDEX_NAME; + } else if (configName.equals("foreignAutoRefresh")) { + return FOREIGN_AUTO_REFRESH; + } else if (configName.equals("maxForeignAutoRefreshLevel")) { + return MAX_FOREIGN_AUTO_REFRESH_LEVEL; + } else if (configName.equals("persisterClass")) { + return PERSISTER_CLASS; + } else if (configName.equals("allowGeneratedIdInsert")) { + return ALLOW_GENERATED_ID_INSERT; + } else if (configName.equals("columnDefinition")) { + return COLUMN_DEFINITON; + } else if (configName.equals("foreignAutoCreate")) { + return FOREIGN_AUTO_CREATE; + } else if (configName.equals("version")) { + return VERSION; + } else if (configName.equals("foreignColumnName")) { + return FOREIGN_COLUMN_NAME; + } else if (configName.equals("readOnly")) { + return READ_ONLY; + } else { + throw new IllegalStateException("Could not find support for DatabaseField " + configName); + } + } + + /** + * Extract our configuration information from the field by looking for a {@link DatabaseField} annotation. + */ + private static DatabaseFieldConfig configFromField(DatabaseType databaseType, String tableName, Field field) + throws SQLException { + + if (configFieldNums == null) { + return DatabaseFieldConfig.fromField(databaseType, tableName, field); + } + + /* + * This, unfortunately, we can't get around. This creates a AnnotationFactory, an array of AnnotationMember + * fields, and possibly another array of AnnotationMember values. This creates a lot of GC'd objects. + */ + DatabaseField databaseField = field.getAnnotation(DatabaseField.class); + + DatabaseFieldConfig config = null; + try { + if (databaseField != null) { + config = buildConfig(databaseField, tableName, field); + } + } catch (Exception e) { + // ignored so we will configure normally below + } + + if (config == null) { + /* + * We configure this the old way because we might be using javax annotations, have a ForeignCollectionField, + * or may still be using the deprecated annotations. At this point we know that there isn't a @DatabaseField + * or we can't do our reflection hacks for some reason. + */ + return DatabaseFieldConfig.fromField(databaseType, tableName, field); + } else { + workedC++; + return config; + } + } + + /** + * Instead of calling the annotation methods directly, we peer inside the proxy and investigate the array of + * AnnotationMember objects stored by the AnnotationFactory. + */ + private static DatabaseFieldConfig buildConfig(DatabaseField databaseField, String tableName, Field field) + throws Exception { + InvocationHandler proxy = Proxy.getInvocationHandler(databaseField); + if (proxy.getClass() != annotationFactoryClazz) { + return null; + } + // this should be an array of AnnotationMember objects + Object elementsObject = elementsField.get(proxy); + if (elementsObject == null) { + return null; + } + DatabaseFieldConfig config = new DatabaseFieldConfig(field.getName()); + Object[] objs = (Object[]) elementsObject; + for (int i = 0; i < configFieldNums.length; i++) { + Object value = valueField.get(objs[i]); + if (value != null) { + assignConfigField(configFieldNums[i], config, field, value); + } + } + return config; + } + + /** + * Converts from field/value from the {@link DatabaseField} annotation to {@link DatabaseFieldConfig} values. This + * is very specific to this annotation. + */ + private static void assignConfigField(int configNum, DatabaseFieldConfig config, Field field, Object value) { + switch (configNum) { + case COLUMN_NAME : + config.setColumnName(valueIfNotBlank((String) value)); + break; + case DATA_TYPE : + config.setDataType((DataType) value); + break; + case DEFAULT_VALUE : + String defaultValue = (String) value; + if (!(defaultValue == null || defaultValue.equals(DatabaseField.DEFAULT_STRING))) { + config.setDefaultValue(defaultValue); + } + break; + case WIDTH : + config.setWidth((Integer) value); + break; + case CAN_BE_NULL : + config.setCanBeNull((Boolean) value); + break; + case ID : + config.setId((Boolean) value); + break; + case GENERATED_ID : + config.setGeneratedId((Boolean) value); + break; + case GENERATED_ID_SEQUENCE : + config.setGeneratedIdSequence(valueIfNotBlank((String) value)); + break; + case FOREIGN : + config.setForeign((Boolean) value); + break; + case USE_GET_SET : + config.setUseGetSet((Boolean) value); + break; + case UNKNOWN_ENUM_NAME : + config.setUnknownEnumValue(DatabaseFieldConfig.findMatchingEnumVal(field, (String) value)); + break; + case THROW_IF_NULL : + config.setThrowIfNull((Boolean) value); + break; + case PERSISTED : + config.setPersisted((Boolean) value); + break; + case FORMAT : + config.setFormat(valueIfNotBlank((String) value)); + break; + case UNIQUE : + config.setUnique((Boolean) value); + break; + case UNIQUE_COMBO : + config.setUniqueCombo((Boolean) value); + break; + case INDEX : + config.setIndex((Boolean) value); + break; + case UNIQUE_INDEX : + config.setUniqueIndex((Boolean) value); + break; + case INDEX_NAME : + config.setIndexName(valueIfNotBlank((String) value)); + break; + case UNIQUE_INDEX_NAME : + config.setUniqueIndexName(valueIfNotBlank((String) value)); + break; + case FOREIGN_AUTO_REFRESH : + config.setForeignAutoRefresh((Boolean) value); + break; + case MAX_FOREIGN_AUTO_REFRESH_LEVEL : + config.setMaxForeignAutoRefreshLevel((Integer) value); + break; + case PERSISTER_CLASS : + @SuppressWarnings("unchecked") + Class clazz = (Class) value; + config.setPersisterClass(clazz); + break; + case ALLOW_GENERATED_ID_INSERT : + config.setAllowGeneratedIdInsert((Boolean) value); + break; + case COLUMN_DEFINITON : + config.setColumnDefinition(valueIfNotBlank((String) value)); + break; + case FOREIGN_AUTO_CREATE : + config.setForeignAutoCreate((Boolean) value); + break; + case VERSION : + config.setVersion((Boolean) value); + break; + case FOREIGN_COLUMN_NAME : + config.setForeignColumnName(valueIfNotBlank((String) value)); + break; + case READ_ONLY : + config.setReadOnly((Boolean) value); + break; + default : + throw new IllegalStateException("Could not find support for DatabaseField number " + configNum); + } + } + + private static String valueIfNotBlank(String value) { + if (value == null || value.length() == 0) { + return null; + } else { + return value; + } + } + + /** + * Class used to investigate the @DatabaseField annotation. + */ + private static class DatabaseFieldSample { + @DatabaseField + String field; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/LICENSE.txt b/MemeProject/app/src/main/java/com/j256/ormlite/android/LICENSE.txt new file mode 100644 index 0000000..ff0417e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/LICENSE.txt @@ -0,0 +1,17 @@ +Copyright 2010 to 2011 by Gray Watson + +This file is part of the ORMLite package. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +The author may be contacted via http://ormlite.sourceforge.net/ diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/README.txt b/MemeProject/app/src/main/java/com/j256/ormlite/android/README.txt new file mode 100644 index 0000000..103b92f --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/README.txt @@ -0,0 +1,14 @@ +This package provides the Android specific functionality. You will also need to download the +ormlite-core package as well. Users that are connecting to SQL databases via JDBC connections +should download the ormlite-jdbc package instead of this Android one. + +For more information, see the online documentation on the home page: + + http://ormlite.com/ + +Sources can be found online via Github: + + https://github.com/j256/ormlite-android + +Enjoy, +Gray Watson diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/BaseOrmLiteLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/BaseOrmLiteLoader.java new file mode 100644 index 0000000..464c81c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/BaseOrmLiteLoader.java @@ -0,0 +1,93 @@ +package com.j256.ormlite.android.apptools; + +import java.util.List; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.Dao.DaoObserver; + +/** + * An abstract superclass for the ORMLite Loader classes, which closely resembles to the Android's + * CursorLoader. Implements basic loading and synchronization logic. + * + * @author EgorAnd + */ +public abstract class BaseOrmLiteLoader extends AsyncTaskLoader> implements DaoObserver { + + /** + * A Dao which will be queried for the data. + */ + protected Dao dao; + private List cachedResults; + + public BaseOrmLiteLoader(Context context) { + super(context); + } + + public BaseOrmLiteLoader(Context context, Dao dao) { + super(context); + this.dao = dao; + } + + @Override + public void deliverResult(List results) { + if (!isReset() && isStarted()) { + super.deliverResult(results); + } + } + + /** + * Starts an asynchronous load of the data. When the result is ready the callbacks will be called on the UI thread. + * If a previous load has been completed and is still valid the result may be passed to the callbacks immediately. + * + *

+ * Must be called from the UI thread. + *

+ */ + @Override + protected void onStartLoading() { + // XXX: do we really return the cached results _before_ checking if the content has changed? + if (cachedResults != null) { + deliverResult(cachedResults); + } + if (takeContentChanged() || cachedResults == null) { + forceLoad(); + } + // watch for data changes + dao.registerObserver(this); + } + + /** + * Must be called from the UI thread + */ + @Override + protected void onStopLoading() { + // attempt to cancel the current load task if possible. + cancelLoad(); + } + + @Override + protected void onReset() { + super.onReset(); + + // ensure the loader is stopped + onStopLoading(); + if (cachedResults != null) { + cachedResults.clear(); + cachedResults = null; + } + + // stop watching for changes + dao.unregisterObserver(this); + } + + public void onChange() { + onContentChanged(); + } + + public void setDao(Dao dao) { + this.dao = dao; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OpenHelperManager.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OpenHelperManager.java new file mode 100644 index 0000000..0609ac2 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OpenHelperManager.java @@ -0,0 +1,282 @@ +package com.j256.ormlite.android.apptools; + +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import android.content.Context; +import android.content.res.Resources; +import android.database.sqlite.SQLiteOpenHelper; + +import com.j256.ormlite.dao.BaseDaoImpl; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; + +/** + * This helps organize and access database connections to optimize connection sharing. There are several schemes to + * manage the database connections in an Android app, but as an app gets more complicated, there are many potential + * places where database locks can occur. This class allows database connection sharing between multiple threads in a + * single app. + * + * This gets injected or called with the {@link OrmLiteSqliteOpenHelper} class that is used to manage the database + * connection. The helper instance will be kept in a static field and only released once its internal usage count goes + * to 0. + * + * The {@link SQLiteOpenHelper} and database classes maintain one connection under the hood, and prevent locks in the + * java code. Creating multiple connections can potentially be a source of trouble. This class shares the same + * connection instance between multiple clients, which will allow multiple activities and services to run at the same + * time. + * + * Every time you use the helper, you should call {@link #getHelper(Context)} or {@link #getHelper(Context, Class)}. + * When you are done with the helper you should call {@link #releaseHelper()}. + * + * @author graywatson, kevingalligan + */ +public class OpenHelperManager { + + private static final String HELPER_CLASS_RESOURCE_NAME = "open_helper_classname"; + private static Logger logger = LoggerFactory.getLogger(OpenHelperManager.class); + + private static Class helperClass = null; + private static volatile OrmLiteSqliteOpenHelper helper = null; + private static boolean wasClosed = false; + private static int instanceCount = 0; + + /** + * If you are _not_ using the {@link OrmLiteBaseActivity} type classes then you will need to call this in a static + * method in your code. + */ + public static synchronized void setOpenHelperClass(Class openHelperClass) { + if (openHelperClass == null) { + helperClass = null; + } else { + innerSetHelperClass(openHelperClass); + } + } + + /** + * Set the helper for the manager. This is most likely used for testing purposes and should only be called if you + * _really_ know what you are doing. If you do use it then it should be in a static {} initializing block to make + * sure you have one helper instance for your application. + */ + public static synchronized void setHelper(OrmLiteSqliteOpenHelper helper) { + OpenHelperManager.helper = helper; + } + + /** + * Create a static instance of our open helper from the helper class. This has a usage counter on it so make sure + * all calls to this method have an associated call to {@link #releaseHelper()}. This should be called during an + * onCreate() type of method when the application or service is starting. The caller should then keep the helper + * around until it is shutting down when {@link #releaseHelper()} should be called. + */ + public static synchronized T getHelper(Context context, Class openHelperClass) { + if (openHelperClass == null) { + throw new IllegalArgumentException("openHelperClass argument is null"); + } + innerSetHelperClass(openHelperClass); + return loadHelper(context, openHelperClass); + } + + /** + *

+ * Similar to {@link #getHelper(Context, Class)} (which is recommended) except we have to find the helper class + * through other means. This method requires that the Context be a class that extends one of ORMLite's Android base + * classes such as {@link OrmLiteBaseActivity}. Either that or the helper class needs to be set in the strings.xml. + *

+ * + *

+ * To find the helper class, this does the following: + *

+ * + *
    + *
  1. If the class has been set with a call to {@link #setOpenHelperClass(Class)}, it will be used to construct a + * helper.
  2. + *
  3. If the resource class name is configured in the strings.xml file it will be used.
  4. + *
  5. The context class hierarchy is walked looking at the generic parameters for a class extending + * OrmLiteSqliteOpenHelper. This is used by the {@link OrmLiteBaseActivity} and other base classes.
  6. + *
  7. An exception is thrown saying that it was not able to set the helper class.
  8. + *
+ * + * @deprecated Should use {@link #getHelper(Context, Class)} + */ + @Deprecated + public static synchronized OrmLiteSqliteOpenHelper getHelper(Context context) { + if (helperClass == null) { + if (context == null) { + throw new IllegalArgumentException("context argument is null"); + } + Context appContext = context.getApplicationContext(); + innerSetHelperClass(lookupHelperClass(appContext, context.getClass())); + } + return loadHelper(context, helperClass); + } + + /** + * @deprecated This has been renamed to be {@link #releaseHelper()}. + */ + @Deprecated + public static void release() { + releaseHelper(); + } + + /** + * Release the helper that was previously returned by a call {@link #getHelper(Context)} or + * {@link #getHelper(Context, Class)}. This will decrement the usage counter and close the helper if the counter is + * 0. + * + *

+ * WARNING: This should be called in an onDestroy() type of method when your application or service is + * terminating or if your code is no longer going to use the helper or derived DAOs in any way. _Don't_ call this + * method if you expect to call {@link #getHelper(Context)} again before the application terminates. + *

+ */ + public static synchronized void releaseHelper() { + instanceCount--; + logger.trace("releasing helper {}, instance count = {}", helper, instanceCount); + if (instanceCount <= 0) { + if (helper != null) { + logger.trace("zero instances, closing helper {}", helper); + helper.close(); + helper = null; + wasClosed = true; + } + if (instanceCount < 0) { + logger.error("too many calls to release helper, instance count = {}", instanceCount); + } + } + } + + /** + * Set the helper class and make sure we aren't changing it to another class. + */ + private static void innerSetHelperClass(Class openHelperClass) { + // make sure if that there are not 2 helper classes in an application + if (openHelperClass == null) { + throw new IllegalStateException("Helper class was trying to be reset to null"); + } else if (helperClass == null) { + helperClass = openHelperClass; + } else if (helperClass != openHelperClass) { + throw new IllegalStateException("Helper class was " + helperClass + " but is trying to be reset to " + + openHelperClass); + } + } + + private static T loadHelper(Context context, Class openHelperClass) { + if (helper == null) { + if (wasClosed) { + // this can happen if you are calling get/release and then get again + logger.info("helper was already closed and is being re-opened"); + } + if (context == null) { + throw new IllegalArgumentException("context argument is null"); + } + Context appContext = context.getApplicationContext(); + helper = constructHelper(appContext, openHelperClass); + logger.trace("zero instances, created helper {}", helper); + /* + * Filipe Leandro and I worked on this bug for like 10 hours straight. It's a doosey. + * + * Each ForeignCollection has internal DAO objects that are holding a ConnectionSource. Each Android + * ConnectionSource is tied to a particular database connection. What Filipe was seeing was that when all of + * his views we closed (onDestroy), but his application WAS NOT FULLY KILLED, the first View.onCreate() + * method would open a new connection to the database. Fine. But because he application was still in memory, + * the static BaseDaoImpl default cache had not been cleared and was containing cached objects with + * ForeignCollections. The ForeignCollections still had references to the DAOs that had been opened with old + * ConnectionSource objects and therefore the old database connection. Using those cached collections would + * cause exceptions saying that you were trying to work with a database that had already been close. + * + * Now, whenever we create a new helper object, we must make sure that the internal object caches have been + * fully cleared. This is a good lesson for anyone that is holding objects around after they have closed + * connections to the database or re-created the DAOs on a different connection somehow. + */ + BaseDaoImpl.clearAllInternalObjectCaches(); + /* + * Might as well do this also since if the helper changes then the ConnectionSource will change so no one is + * going to have a cache hit on the old DAOs anyway. All they are doing is holding memory. + * + * NOTE: we don't want to clear the config map. + */ + DaoManager.clearDaoCache(); + instanceCount = 0; + } + + instanceCount++; + logger.trace("returning helper {}, instance count = {} ", helper, instanceCount); + @SuppressWarnings("unchecked") + T castHelper = (T) helper; + return castHelper; + } + + /** + * Call the constructor on our helper class. + */ + private static OrmLiteSqliteOpenHelper constructHelper(Context context, + Class openHelperClass) { + Constructor constructor; + try { + constructor = openHelperClass.getConstructor(Context.class); + } catch (Exception e) { + throw new IllegalStateException( + "Could not find public constructor that has a single (Context) argument for helper class " + + openHelperClass, e); + } + try { + return (OrmLiteSqliteOpenHelper) constructor.newInstance(context); + } catch (Exception e) { + throw new IllegalStateException("Could not construct instance of helper class " + openHelperClass, e); + } + } + + /** + * Lookup the helper class either from the resource string or by looking for a generic parameter. + */ + private static Class lookupHelperClass(Context context, Class componentClass) { + + // see if we have the magic resource class name set + Resources resources = context.getResources(); + int resourceId = resources.getIdentifier(HELPER_CLASS_RESOURCE_NAME, "string", context.getPackageName()); + if (resourceId != 0) { + String className = resources.getString(resourceId); + try { + @SuppressWarnings("unchecked") + Class castClass = + (Class) Class.forName(className); + return castClass; + } catch (Exception e) { + throw new IllegalStateException("Could not create helper instance for class " + className, e); + } + } + + // try walking the context class to see if we can get the OrmLiteSqliteOpenHelper from a generic parameter + for (Class componentClassWalk = componentClass; componentClassWalk != null; componentClassWalk = + componentClassWalk.getSuperclass()) { + Type superType = componentClassWalk.getGenericSuperclass(); + if (superType == null || !(superType instanceof ParameterizedType)) { + continue; + } + // get the generic type arguments + Type[] types = ((ParameterizedType) superType).getActualTypeArguments(); + // defense + if (types == null || types.length == 0) { + continue; + } + for (Type type : types) { + // defense + if (!(type instanceof Class)) { + continue; + } + Class clazz = (Class) type; + if (OrmLiteSqliteOpenHelper.class.isAssignableFrom(clazz)) { + @SuppressWarnings("unchecked") + Class castOpenHelperClass = + (Class) clazz; + return castOpenHelperClass; + } + } + } + throw new IllegalStateException( + "Could not find OpenHelperClass because none of the generic parameters of class " + componentClass + + " extends OrmLiteSqliteOpenHelper. You should use getHelper(Context, Class) instead."); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivity.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivity.java new file mode 100644 index 0000000..ccad28a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivity.java @@ -0,0 +1,108 @@ +package com.j256.ormlite.android.apptools; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; + +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.support.ConnectionSource; + +/** + * Base class to use for activities in Android. + * + * You can simply call {@link #getHelper()} to get your helper class, or {@link #getConnectionSource()} to get a + * {@link ConnectionSource}. + * + * The method {@link #getHelper()} assumes you are using the default helper factory -- see {@link OpenHelperManager}. If + * not, you'll need to provide your own helper instances which will need to implement a reference counting scheme. This + * method will only be called if you use the database, and only called once for this activity's life-cycle. 'close' will + * also be called once for each call to createInstance. + * + * @author graywatson, kevingalligan + */ +public abstract class OrmLiteBaseActivity extends Activity { + + private volatile H helper; + private volatile boolean created = false; + private volatile boolean destroyed = false; + private static Logger logger = LoggerFactory.getLogger(OrmLiteBaseActivity.class); + + /** + * Get a helper for this action. + */ + public H getHelper() { + if (helper == null) { + if (!created) { + throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException("Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource() { + return getHelper().getConnectionSource(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (helper == null) { + helper = getHelperInternal(this); + created = true; + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseHelper(helper); + destroyed = true; + } + + /** + * This is called internally by the class to populate the helper object instance. This should not be called directly + * by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are + * managing your own helper creation, override this method to supply this activity with a helper instance. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well. + *

+ */ + protected H getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked", "deprecation" }) + H newHelper = (H) OpenHelperManager.getHelper(context); + logger.trace("{}: got new helper {} from OpenHelperManager", this, newHelper); + return newHelper; + } + + /** + * Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call + * this directly since {@link #onDestroy()} does it for you. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #getHelperInternal(Context)} method as well. + *

+ */ + protected void releaseHelper(H helper) { + OpenHelperManager.releaseHelper(); + logger.trace("{}: helper {} was released, set to null", this, helper); + this.helper = null; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivityGroup.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivityGroup.java new file mode 100644 index 0000000..66ad889 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseActivityGroup.java @@ -0,0 +1,99 @@ +package com.j256.ormlite.android.apptools; + +import android.app.ActivityGroup; +import android.content.Context; +import android.os.Bundle; + +import com.j256.ormlite.support.ConnectionSource; + +/** + * Base class to use for activity groups in Android. + * + * You can simply call {@link #getHelper()} to get your helper class, or {@link #getConnectionSource()} to get a + * {@link ConnectionSource}. + * + * The method {@link #getHelper()} assumes you are using the default helper factory -- see {@link OpenHelperManager}. If + * not, you'll need to provide your own helper instances which will need to implement a reference counting scheme. This + * method will only be called if you use the database, and only called once for this activity's life-cycle. 'close' will + * also be called once for each call to createInstance. + * + * @author graywatson, kevingalligan + */ +@SuppressWarnings("deprecation") +public abstract class OrmLiteBaseActivityGroup extends ActivityGroup { + + private volatile H helper; + private volatile boolean created = false; + private volatile boolean destroyed = false; + + /** + * Get a helper for this action. + */ + public H getHelper() { + if (helper == null) { + if (!created) { + throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException("Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource() { + return getHelper().getConnectionSource(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (helper == null) { + helper = getHelperInternal(this); + created = true; + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseHelper(helper); + destroyed = true; + } + + /** + * This is called internally by the class to populate the helper object instance. This should not be called directly + * by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are + * managing your own helper creation, override this method to supply this activity with a helper instance. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well. + *

+ */ + protected H getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked" }) + H newHelper = (H) OpenHelperManager.getHelper(context); + return newHelper; + } + + /** + * Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call + * this directly since {@link #onDestroy()} does it for you. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #getHelperInternal(Context)} method as well. + *

+ */ + protected void releaseHelper(H helper) { + OpenHelperManager.releaseHelper(); + this.helper = null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseListActivity.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseListActivity.java new file mode 100644 index 0000000..cb317ab --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseListActivity.java @@ -0,0 +1,92 @@ +package com.j256.ormlite.android.apptools; + +import android.app.ListActivity; +import android.content.Context; +import android.os.Bundle; + +import com.j256.ormlite.support.ConnectionSource; + +/** + * Base class to use for Tab activities in Android. + * + * For more information, see {@link OrmLiteBaseActivity}. + * + * @author graywatson, kevingalligan + */ +public abstract class OrmLiteBaseListActivity extends ListActivity { + + private volatile H helper; + private volatile boolean created = false; + private volatile boolean destroyed = false; + + /** + * Get a helper for this action. + */ + public H getHelper() { + if (helper == null) { + if (!created) { + throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException("Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource() { + return getHelper().getConnectionSource(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (helper == null) { + helper = getHelperInternal(this); + created = true; + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseHelper(helper); + destroyed = true; + } + + /** + * This is called internally by the class to populate the helper object instance. This should not be called directly + * by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are + * managing your own helper creation, override this method to supply this activity with a helper instance. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well. + *

+ */ + protected H getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked", "deprecation" }) + H newHelper = (H) OpenHelperManager.getHelper(context); + return newHelper; + } + + /** + * Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call + * this directly since {@link #onDestroy()} does it for you. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #getHelperInternal(Context)} method as well. + *

+ */ + protected void releaseHelper(H helper) { + OpenHelperManager.releaseHelper(); + this.helper = null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseService.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseService.java new file mode 100644 index 0000000..4afb256 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseService.java @@ -0,0 +1,91 @@ +package com.j256.ormlite.android.apptools; + +import android.app.Service; +import android.content.Context; + +import com.j256.ormlite.support.ConnectionSource; + +/** + * Base class to use for services in Android. + * + * For more information, see {@link OrmLiteBaseActivity}. + * + * @author graywatson, kevingalligan + */ +public abstract class OrmLiteBaseService extends Service { + + private volatile H helper; + private volatile boolean created = false; + private volatile boolean destroyed = false; + + /** + * Get a helper for this action. + */ + public H getHelper() { + if (helper == null) { + if (!created) { + throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException("Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource() { + return getHelper().getConnectionSource(); + } + + @Override + public void onCreate() { + if (helper == null) { + helper = getHelperInternal(this); + created = true; + } + super.onCreate(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + releaseHelper(helper); + destroyed = true; + } + + /** + * This is called internally by the class to populate the helper object instance. This should not be called directly + * by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are + * managing your own helper creation, override this method to supply this activity with a helper instance. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well. + *

+ */ + protected H getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked", "deprecation" }) + H newHelper = (H) OpenHelperManager.getHelper(context); + return newHelper; + } + + /** + * Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call + * this directly since {@link #onDestroy()} does it for you. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #getHelperInternal(Context)} method as well. + *

+ */ + protected void releaseHelper(H helper) { + OpenHelperManager.releaseHelper(); + this.helper = null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseTabActivity.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseTabActivity.java new file mode 100644 index 0000000..9e0080a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteBaseTabActivity.java @@ -0,0 +1,95 @@ +package com.j256.ormlite.android.apptools; + +import android.app.TabActivity; +import android.content.Context; +import android.os.Bundle; + +import com.j256.ormlite.support.ConnectionSource; + +/** + * Base class to use for Tab activities in Android. + * + * For more information, see {@link OrmLiteBaseActivity}. + * + * @author graywatson, kevingalligan + */ +@SuppressWarnings("deprecation") +public abstract class OrmLiteBaseTabActivity extends TabActivity { + + private volatile H helper; + private volatile boolean created = false; + private volatile boolean destroyed = false; + + /** + * Get a helper for this action. + */ + public H getHelper() { + if (helper == null) { + if (!created) { + throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException("Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource() { + return getHelper().getConnectionSource(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (helper == null) { + helper = getHelperInternal(this); + created = true; + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseHelper(helper); + destroyed = true; + } + + /** + * This is called internally by the class to populate the helper object instance. This should not be called directly + * by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are + * managing your own helper creation, override this method to supply this activity with a helper instance. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well. + *

+ * + * @see OpenHelperManager#getHelper(Context) + */ + protected H getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked" }) + H newHelper = (H) OpenHelperManager.getHelper(context); + return newHelper; + } + + /** + * Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call + * this directly since {@link #onDestroy()} does it for you. + * + *

+ * NOTE: If you override this method, you most likely will need to override the + * {@link #getHelperInternal(Context)} method as well. + *

+ */ + protected void releaseHelper(H helper) { + OpenHelperManager.releaseHelper(); + this.helper = null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteConfigUtil.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteConfigUtil.java new file mode 100644 index 0000000..52c4596 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteConfigUtil.java @@ -0,0 +1,339 @@ +package com.j256.ormlite.android.apptools; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileFilter; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.db.SqliteAndroidDatabaseType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.DatabaseFieldConfig; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.table.DatabaseTable; +import com.j256.ormlite.table.DatabaseTableConfig; +import com.j256.ormlite.table.DatabaseTableConfigLoader; + +/** + * Database configuration file helper class that is used to write a configuration file into the raw resource + * sub-directory to speed up DAO creation. + * + *

+ * With help from the user list and especially Ian Dees, we discovered that calls to annotation methods in Android are + * _very_ expensive because Method.equals() was doing a huge toString(). This was causing folks to see 2-3 seconds + * startup time when configuring 10-15 DAOs because of 1000s of calls to @DatabaseField methods. See this Android bug report. + *

+ * + *

+ * I added this utility class which writes a configuration file into the raw resource "res/raw" directory inside of your + * project containing the table and field names and associated details. This file can then be loaded into the + * {@link DaoManager} with the help of the + * {@link OrmLiteSqliteOpenHelper#OrmLiteSqliteOpenHelper(android.content.Context, String, android.database.sqlite.SQLiteDatabase.CursorFactory, int, int)} + * constructor. This means that you can configure your classes _without_ any runtime calls to annotations. It seems + * significantly faster. + *

+ * + *

+ * WARNING: Although this is fast, the big problem is that you have to remember to regenerate the config file + * whenever you edit one of your database classes. There is no way that I know of to do this automagically. + *

+ * + * @author graywatson + */ +public class OrmLiteConfigUtil { + + /** + * Resource directory name that we are looking for. + */ + protected static final String RESOURCE_DIR_NAME = "res"; + /** + * Raw directory name that we are looking for. + */ + protected static final String RAW_DIR_NAME = "raw"; + + /** + * Maximum recursion level while we are looking for source files. + */ + protected static int maxFindSourceLevel = 20; + + private static final DatabaseType databaseType = new SqliteAndroidDatabaseType(); + + /** + * A call through to {@link #writeConfigFile(String)} taking the file name from the single command line argument. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + throw new IllegalArgumentException("Main can take a single file-name argument."); + } + writeConfigFile(args[0]); + } + + /** + * Finds the annotated classes in the current directory or below and writes a configuration file to the file-name in + * the raw folder. + */ + public static void writeConfigFile(String fileName) throws SQLException, IOException { + List> classList = new ArrayList>(); + findAnnotatedClasses(classList, new File("."), 0); + writeConfigFile(fileName, classList.toArray(new Class[classList.size()])); + } + + /** + * Writes a configuration fileName in the raw directory with the configuration for classes. + */ + public static void writeConfigFile(String fileName, Class[] classes) throws SQLException, IOException { + File rawDir = findRawDir(new File(".")); + if (rawDir == null) { + System.err.println("Could not find " + RAW_DIR_NAME + " directory which is typically in the " + + RESOURCE_DIR_NAME + " directory"); + } else { + File configFile = new File(rawDir, fileName); + writeConfigFile(configFile, classes); + } + } + + /** + * Finds the annotated classes in the current directory or below and writes a configuration file. + */ + public static void writeConfigFile(File configFile) throws SQLException, IOException { + writeConfigFile(configFile, new File(".")); + } + + /** + * Finds the annotated classes in the specified search directory or below and writes a configuration file. + */ + public static void writeConfigFile(File configFile, File searchDir) throws SQLException, IOException { + List> classList = new ArrayList>(); + findAnnotatedClasses(classList, searchDir, 0); + writeConfigFile(configFile, classList.toArray(new Class[classList.size()])); + } + + /** + * Write a configuration file with the configuration for classes. + */ + public static void writeConfigFile(File configFile, Class[] classes) throws SQLException, IOException { + System.out.println("Writing configurations to " + configFile.getAbsolutePath()); + writeConfigFile(new FileOutputStream(configFile), classes); + } + + /** + * Write a configuration file to an output stream with the configuration for classes. + */ + public static void writeConfigFile(OutputStream outputStream, File searchDir) throws SQLException, IOException { + List> classList = new ArrayList>(); + findAnnotatedClasses(classList, searchDir, 0); + writeConfigFile(outputStream, classList.toArray(new Class[classList.size()])); + } + + /** + * Write a configuration file to an output stream with the configuration for classes. + */ + public static void writeConfigFile(OutputStream outputStream, Class[] classes) throws SQLException, IOException { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096); + try { + writeHeader(writer); + for (Class clazz : classes) { + writeConfigForTable(writer, clazz); + } + // NOTE: done is here because this is public + System.out.println("Done."); + } finally { + writer.close(); + } + } + + /** + * Look for the resource-directory in the current directory or the directories above. Then look for the + * raw-directory underneath the resource-directory. + */ + protected static File findRawDir(File dir) { + for (int i = 0; dir != null && i < 20; i++) { + File rawDir = findResRawDir(dir); + if (rawDir != null) { + return rawDir; + } + dir = dir.getParentFile(); + } + return null; + } + + private static void writeHeader(BufferedWriter writer) throws IOException { + writer.append('#'); + writer.newLine(); + writer.append("# generated on ").append(new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(new Date())); + writer.newLine(); + writer.append('#'); + writer.newLine(); + } + + private static void findAnnotatedClasses(List> classList, File dir, int level) throws SQLException, + IOException { + for (File file : dir.listFiles()) { + if (file.isDirectory()) { + // recurse if we aren't deep enough + if (level < maxFindSourceLevel) { + findAnnotatedClasses(classList, file, level + 1); + } + continue; + } + // skip non .java files + if (!file.getName().endsWith(".java")) { + continue; + } + String packageName = getPackageOfClass(file); + if (packageName == null) { + System.err.println("Could not find package name for: " + file); + continue; + } + // get the filename and cut off the .java + String name = file.getName(); + name = name.substring(0, name.length() - ".java".length()); + String className = packageName + "." + name; + Class clazz; + try { + clazz = Class.forName(className); + } catch (Throwable t) { + // amazingly, this sometimes throws an Error + System.err.println("Could not load class file for: " + file); + System.err.println(" " + t); + continue; + } + if (classHasAnnotations(clazz)) { + classList.add(clazz); + } + // handle inner classes + try { + for (Class innerClazz : clazz.getDeclaredClasses()) { + if (classHasAnnotations(innerClazz)) { + classList.add(innerClazz); + } + } + } catch (Throwable t) { + // amazingly, this sometimes throws an Error + System.err.println("Could not load inner classes for: " + clazz); + System.err.println(" " + t); + continue; + } + } + } + + private static void writeConfigForTable(BufferedWriter writer, Class clazz) throws SQLException, IOException { + String tableName = DatabaseTableConfig.extractTableName(clazz); + List fieldConfigs = new ArrayList(); + // walk up the classes finding the fields + try { + for (Class working = clazz; working != null; working = working.getSuperclass()) { + for (Field field : working.getDeclaredFields()) { + DatabaseFieldConfig fieldConfig = DatabaseFieldConfig.fromField(databaseType, tableName, field); + if (fieldConfig != null) { + fieldConfigs.add(fieldConfig); + } + } + } + } catch (Error e) { + System.err.println("Skipping " + clazz + " because we got an error finding its definition: " + + e.getMessage()); + return; + } + if (fieldConfigs.isEmpty()) { + System.out.println("Skipping " + clazz + " because no annotated fields found"); + return; + } + @SuppressWarnings({ "rawtypes", "unchecked" }) + DatabaseTableConfig tableConfig = new DatabaseTableConfig(clazz, tableName, fieldConfigs); + DatabaseTableConfigLoader.write(writer, tableConfig); + writer.append("#################################"); + writer.newLine(); + System.out.println("Wrote config for " + clazz); + } + + private static boolean classHasAnnotations(Class clazz) { + while (clazz != null) { + if (clazz.getAnnotation(DatabaseTable.class) != null) { + return true; + } + Field[] fields; + try { + fields = clazz.getDeclaredFields(); + } catch (Throwable t) { + // amazingly, this sometimes throws an Error + System.err.println("Could not load get delcared fields from: " + clazz); + System.err.println(" " + t); + return false; + } + for (Field field : fields) { + if (field.getAnnotation(DatabaseField.class) != null + || field.getAnnotation(ForeignCollectionField.class) != null) { + return true; + } + } + try { + clazz = clazz.getSuperclass(); + } catch (Throwable t) { + // amazingly, this sometimes throws an Error + System.err.println("Could not get super class for: " + clazz); + System.err.println(" " + t); + return false; + } + } + + return false; + } + + /** + * Returns the package name of a file that has one of the annotations we are looking for. + * + * @return Package prefix string or null or no annotations. + */ + private static String getPackageOfClass(File file) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(file)); + try { + while (true) { + String line = reader.readLine(); + if (line == null) { + return null; + } + if (line.contains("package")) { + String[] parts = line.split("[ \t;]"); + if (parts.length > 1 && parts[0].equals("package")) { + return parts[1]; + } + } + } + } finally { + reader.close(); + } + } + + /** + * Look for the resource directory with raw beneath it. + */ + private static File findResRawDir(File dir) { + for (File file : dir.listFiles()) { + if (file.getName().equals(RESOURCE_DIR_NAME) && file.isDirectory()) { + File[] rawFiles = file.listFiles(new FileFilter() { + public boolean accept(File file) { + return file.getName().equals(RAW_DIR_NAME) && file.isDirectory(); + } + }); + if (rawFiles.length == 1) { + return rawFiles[0]; + } + } + } + return null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorAdapter.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorAdapter.java new file mode 100644 index 0000000..3e9954d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorAdapter.java @@ -0,0 +1,93 @@ +package com.j256.ormlite.android.apptools; + +import java.sql.SQLException; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.widget.CursorAdapter; + +import com.j256.ormlite.android.AndroidDatabaseResults; +import com.j256.ormlite.stmt.PreparedQuery; + +/** + * Cursor adapter base class. + * + * @author emmby + */ +public abstract class OrmLiteCursorAdapter extends CursorAdapter { + + protected PreparedQuery preparedQuery; + + public OrmLiteCursorAdapter(Context context) { + super(context, null, false); + } + + /** + * Bind the view to a particular item. + */ + public abstract void bindView(ViewType itemView, Context context, T item); + + /** + * Final to prevent subclasses from accidentally overriding. Intentional overriding can be accomplished by + * overriding {@link #doBindView(View, Context, Cursor)}. + * + * @see CursorAdapter#bindView(View, Context, Cursor) + */ + @Override + public final void bindView(View itemView, Context context, Cursor cursor) { + doBindView(itemView, context, cursor); + } + + /** + * This is here to make sure that the user really wants to override it. + */ + protected void doBindView(View itemView, Context context, Cursor cursor) { + try { + @SuppressWarnings("unchecked") + ViewType itemViewType = (ViewType) itemView; + bindView(itemViewType, context, cursorToObject(cursor)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns a T object at the current position. + */ + public T getTypedItem(int position) { + try { + return cursorToObject((Cursor) super.getItem(position)); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Map a single row to our cursor object. + */ + protected T cursorToObject(Cursor cursor) throws SQLException { + return preparedQuery.mapRow(new AndroidDatabaseResults(cursor, null)); + } + + /** + * Show not be used. Instead use {@link #changeCursor(Cursor, PreparedQuery)} + */ + @Override + public final void changeCursor(Cursor cursor) { + throw new UnsupportedOperationException( + "Please use OrmLiteCursorAdapter.changeCursor(Cursor,PreparedQuery) instead"); + } + + /** + * Change the cursor associated with the prepared query. + */ + public void changeCursor(Cursor cursor, PreparedQuery preparedQuery) { + setPreparedQuery(preparedQuery); + super.changeCursor(cursor); + } + + public void setPreparedQuery(PreparedQuery preparedQuery) { + this.preparedQuery = preparedQuery; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorLoader.java new file mode 100644 index 0000000..03ac9cf --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteCursorLoader.java @@ -0,0 +1,131 @@ +package com.j256.ormlite.android.apptools; + +import static com.j256.ormlite.stmt.StatementBuilder.StatementType.SELECT; + +import java.sql.SQLException; + +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.database.Cursor; + +import com.j256.ormlite.android.AndroidCompiledStatement; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.Dao.DaoObserver; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.support.DatabaseConnection; + +/** + * Cursor loader supported by later Android APIs that allows asynchronous content loading. + * + *

+ * NOTE: This should be the same as {@link com.j256.ormlite.android.apptools.support.OrmLiteCursorLoader} + * but this should import the normal version of the {@link AsyncTaskLoader}, not the support version. + *

+ * + * @author emmby + */ +public class OrmLiteCursorLoader extends AsyncTaskLoader implements DaoObserver { + + protected Dao dao; + protected PreparedQuery query; + protected Cursor cursor; + + public OrmLiteCursorLoader(Context context, Dao dao, PreparedQuery query) { + super(context); + this.dao = dao; + this.query = query; + } + + @Override + public Cursor loadInBackground() { + Cursor cursor; + try { + DatabaseConnection connection = dao.getConnectionSource().getReadOnlyConnection(); + AndroidCompiledStatement statement = (AndroidCompiledStatement) query.compile(connection, SELECT); + cursor = statement.getCursor(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + + // fill the cursor with results + cursor.getCount(); + return cursor; + } + + @Override + public void deliverResult(Cursor newCursor) { + if (isReset()) { + // an async query came in while the loader is stopped + if (newCursor != null) { + newCursor.close(); + } + return; + } + + Cursor oldCursor = cursor; + cursor = newCursor; + + if (isStarted()) { + super.deliverResult(newCursor); + } + + // close the old cursor if necessary + if (oldCursor != null && oldCursor != newCursor && !oldCursor.isClosed()) { + oldCursor.close(); + } + } + + @Override + protected void onStartLoading() { + // start watching for dataset changes + dao.registerObserver(this); + + if (cursor == null) { + forceLoad(); + } else { + deliverResult(cursor); + if (takeContentChanged()) { + forceLoad(); + } + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + @Override + protected void onReset() { + super.onReset(); + onStopLoading(); + if (cursor != null) { + if (!cursor.isClosed()) { + cursor.close(); + } + cursor = null; + } + + // stop watching for changes + dao.unregisterObserver(this); + } + + public void onChange() { + onContentChanged(); + } + + public PreparedQuery getQuery() { + return query; + } + + public void setQuery(PreparedQuery mQuery) { + this.query = mQuery; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLitePreparedQueryLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLitePreparedQueryLoader.java new file mode 100644 index 0000000..4771537 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLitePreparedQueryLoader.java @@ -0,0 +1,55 @@ +package com.j256.ormlite.android.apptools; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import android.content.Context; +import android.content.Loader; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.stmt.PreparedQuery; + +/** + * A {@link Loader} implementation that queries specified {@link Dao} using a {@link PreparedQuery}. + * + * @author Egorand + */ +public class OrmLitePreparedQueryLoader extends BaseOrmLiteLoader { + + private PreparedQuery preparedQuery; + + public OrmLitePreparedQueryLoader(Context context) { + super(context); + } + + public OrmLitePreparedQueryLoader(Context context, Dao dao, PreparedQuery preparedQuery) { + super(context, dao); + this.preparedQuery = preparedQuery; + } + + @Override + public List loadInBackground() { + if (dao == null) { + throw new IllegalStateException("Dao is not initialized."); + } + if (preparedQuery == null) { + throw new IllegalStateException("PreparedQuery is not initialized."); + } + try { + return dao.query(preparedQuery); + } catch (SQLException e) { + // XXX: is this really the right thing to do? Maybe throw RuntimeException? + e.printStackTrace(); + return Collections.emptyList(); + } + } + + public void setPreparedQuery(PreparedQuery preparedQuery) { + this.preparedQuery = preparedQuery; + } + + public PreparedQuery getPreparedQuery() { + return preparedQuery; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteQueryForAllLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteQueryForAllLoader.java new file mode 100644 index 0000000..6add86c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteQueryForAllLoader.java @@ -0,0 +1,40 @@ +package com.j256.ormlite.android.apptools; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import android.content.Context; + +import com.j256.ormlite.dao.Dao; + +/** + * A Loader implementation that queries specified {@link com.j256.ormlite.dao.Dao} for all data, using the + * Dao.queryForAll() call. + * + * @author EgorAnd + */ +public class OrmLiteQueryForAllLoader extends BaseOrmLiteLoader { + + public OrmLiteQueryForAllLoader(Context context) { + super(context); + } + + public OrmLiteQueryForAllLoader(Context context, Dao dao) { + super(context, dao); + } + + @Override + public List loadInBackground() { + if (dao == null) { + throw new IllegalStateException("Dao is not initialized."); + } + try { + return dao.queryForAll(); + } catch (SQLException e) { + // XXX: is this really the right thing to do? Maybe throw RuntimeException? + e.printStackTrace(); + return Collections.emptyList(); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteSqliteOpenHelper.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteSqliteOpenHelper.java new file mode 100644 index 0000000..62a3f45 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/OrmLiteSqliteOpenHelper.java @@ -0,0 +1,326 @@ +package com.j256.ormlite.android.apptools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.SQLException; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.database.sqlite.SQLiteOpenHelper; + +import com.j256.ormlite.android.AndroidConnectionSource; +import com.j256.ormlite.android.AndroidDatabaseConnection; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.dao.RuntimeExceptionDao; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.DatabaseTableConfigLoader; + +/** + * SQLite database open helper which can be extended by your application to help manage when the application needs to + * create or upgrade its database. + * + * @author kevingalligan, graywatson + */ +public abstract class OrmLiteSqliteOpenHelper extends SQLiteOpenHelper { + + protected static Logger logger = LoggerFactory.getLogger(OrmLiteSqliteOpenHelper.class); + protected AndroidConnectionSource connectionSource = new AndroidConnectionSource(this); + + protected boolean cancelQueriesEnabled; + private volatile boolean isOpen = true; + + /** + * @param context + * Associated content from the application. This is needed to locate the database. + * @param databaseName + * Name of the database we are opening. + * @param factory + * Cursor factory or null if none. + * @param databaseVersion + * Version of the database we are opening. This causes {@link #onUpgrade(SQLiteDatabase, int, int)} to be + * called if the stored database is a different version. + */ + public OrmLiteSqliteOpenHelper(Context context, String databaseName, CursorFactory factory, int databaseVersion) { + super(context, databaseName, factory, databaseVersion); + logger.trace("{}: constructed connectionSource {}", this, connectionSource); + } + + /** + * Same as the other constructor with the addition of a file-id of the table config-file. See + * {@link OrmLiteConfigUtil} for details. + * + * @param context + * Associated content from the application. This is needed to locate the database. + * @param databaseName + * Name of the database we are opening. + * @param factory + * Cursor factory or null if none. + * @param databaseVersion + * Version of the database we are opening. This causes {@link #onUpgrade(SQLiteDatabase, int, int)} to be + * called if the stored database is a different version. + * @param configFileId + * file-id which probably should be a R.raw.ormlite_config.txt or some static value. + */ + public OrmLiteSqliteOpenHelper(Context context, String databaseName, CursorFactory factory, int databaseVersion, + int configFileId) { + this(context, databaseName, factory, databaseVersion, openFileId(context, configFileId)); + } + + /** + * Same as the other constructor with the addition of a config-file. See {@link OrmLiteConfigUtil} for details. + * + * @param context + * Associated content from the application. This is needed to locate the database. + * @param databaseName + * Name of the database we are opening. + * @param factory + * Cursor factory or null if none. + * @param databaseVersion + * Version of the database we are opening. This causes {@link #onUpgrade(SQLiteDatabase, int, int)} to be + * called if the stored database is a different version. + * @param configFile + * Configuration file to be loaded. + */ + public OrmLiteSqliteOpenHelper(Context context, String databaseName, CursorFactory factory, int databaseVersion, + File configFile) { + this(context, databaseName, factory, databaseVersion, openFile(configFile)); + } + + /** + * Same as the other constructor with the addition of a input stream to the table config-file. See + * {@link OrmLiteConfigUtil} for details. + * + * @param context + * Associated content from the application. This is needed to locate the database. + * @param databaseName + * Name of the database we are opening. + * @param factory + * Cursor factory or null if none. + * @param databaseVersion + * Version of the database we are opening. This causes {@link #onUpgrade(SQLiteDatabase, int, int)} to be + * called if the stored database is a different version. + * @param stream + * Stream opened to the configuration file to be loaded. It will be closed when this method returns. + */ + public OrmLiteSqliteOpenHelper(Context context, String databaseName, CursorFactory factory, int databaseVersion, + InputStream stream) { + super(context, databaseName, factory, databaseVersion); + if (stream == null) { + return; + } + + // if a config file-id was specified then load it into the DaoManager + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(stream), 4096); + stream = null; + DaoManager.addCachedDatabaseConfigs(DatabaseTableConfigLoader.loadDatabaseConfigFromReader(reader)); + } catch (SQLException e) { + throw new IllegalStateException("Could not load object config file", e); + } finally { + IOUtils.closeQuietly(reader); + IOUtils.closeQuietly(stream); + } + } + + /** + * What to do when your database needs to be created. Usually this entails creating the tables and loading any + * initial data. + * + *

+ * NOTE: You should use the connectionSource argument that is passed into this method call or the one + * returned by getConnectionSource(). If you use your own, a recursive call or other unexpected results may result. + *

+ * + * @param database + * Database being created. + * @param connectionSource + * To use get connections to the database to be created. + */ + public abstract void onCreate(SQLiteDatabase database, ConnectionSource connectionSource); + + /** + * What to do when your database needs to be updated. This could mean careful migration of old data to new data. + * Maybe adding or deleting database columns, etc.. + * + *

+ * NOTE: You should use the connectionSource argument that is passed into this method call or the one + * returned by getConnectionSource(). If you use your own, a recursive call or other unexpected results may result. + *

+ * + * @param database + * Database being upgraded. + * @param connectionSource + * To use get connections to the database to be updated. + * @param oldVersion + * The version of the current database so we can know what to do to the database. + * @param newVersion + * The version that we are upgrading the database to. + */ + public abstract void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, + int newVersion); + + /** + * Get the connection source associated with the helper. + */ + public ConnectionSource getConnectionSource() { + if (!isOpen) { + // we don't throw this exception, but log it for debugging purposes + logger.warn(new IllegalStateException(), "Getting connectionSource was called after closed"); + } + return connectionSource; + } + + /** + * Satisfies the {@link SQLiteOpenHelper#onCreate(SQLiteDatabase)} interface method. + */ + @Override + public final void onCreate(SQLiteDatabase db) { + ConnectionSource cs = getConnectionSource(); + /* + * The method is called by Android database helper's get-database calls when Android detects that we need to + * create or update the database. So we have to use the database argument and save a connection to it on the + * AndroidConnectionSource, otherwise it will go recursive if the subclass calls getConnectionSource(). + */ + DatabaseConnection conn = cs.getSpecialConnection(); + boolean clearSpecial = false; + if (conn == null) { + conn = new AndroidDatabaseConnection(db, true, cancelQueriesEnabled); + try { + cs.saveSpecialConnection(conn); + clearSpecial = true; + } catch (SQLException e) { + throw new IllegalStateException("Could not save special connection", e); + } + } + try { + onCreate(db, cs); + } finally { + if (clearSpecial) { + cs.clearSpecialConnection(conn); + } + } + } + + /** + * Satisfies the {@link SQLiteOpenHelper#onUpgrade(SQLiteDatabase, int, int)} interface method. + */ + @Override + public final void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + ConnectionSource cs = getConnectionSource(); + /* + * The method is called by Android database helper's get-database calls when Android detects that we need to + * create or update the database. So we have to use the database argument and save a connection to it on the + * AndroidConnectionSource, otherwise it will go recursive if the subclass calls getConnectionSource(). + */ + DatabaseConnection conn = cs.getSpecialConnection(); + boolean clearSpecial = false; + if (conn == null) { + conn = new AndroidDatabaseConnection(db, true, cancelQueriesEnabled); + try { + cs.saveSpecialConnection(conn); + clearSpecial = true; + } catch (SQLException e) { + throw new IllegalStateException("Could not save special connection", e); + } + } + try { + onUpgrade(db, cs, oldVersion, newVersion); + } finally { + if (clearSpecial) { + cs.clearSpecialConnection(conn); + } + } + } + + /** + * Close any open connections. + */ + @Override + public void close() { + super.close(); + connectionSource.close(); + /* + * We used to set connectionSource to null here but now we just set the closed flag and then log heavily if + * someone uses getConectionSource() after this point. + */ + isOpen = false; + } + + /** + * Return true if the helper is still open. Once {@link #close()} is called then this will return false. + */ + public boolean isOpen() { + return isOpen; + } + + /** + * Get a DAO for our class. This uses the {@link DaoManager} to cache the DAO for future gets. + * + *

+ * NOTE: This routing does not return Dao<T, ID> because of casting issues if we are assigning it to a custom DAO. + * Grumble. + *

+ */ + public , T> D getDao(Class clazz) throws SQLException { + // special reflection fu is now handled internally by create dao calling the database type + Dao dao = DaoManager.createDao(getConnectionSource(), clazz); + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + /** + * Get a RuntimeExceptionDao for our class. This uses the {@link DaoManager} to cache the DAO for future gets. + * + *

+ * NOTE: This routing does not return RuntimeExceptionDao<T, ID> because of casting issues if we are assigning it to + * a custom DAO. Grumble. + *

+ */ + public , T> D getRuntimeExceptionDao(Class clazz) { + try { + Dao dao = getDao(clazz); + @SuppressWarnings({ "unchecked", "rawtypes" }) + D castDao = (D) new RuntimeExceptionDao(dao); + return castDao; + } catch (SQLException e) { + throw new RuntimeException("Could not create RuntimeExcepitionDao for class " + clazz, e); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); + } + + private static InputStream openFileId(Context context, int fileId) { + InputStream stream = context.getResources().openRawResource(fileId); + if (stream == null) { + throw new IllegalStateException("Could not find object config file with id " + fileId); + } + return stream; + } + + private static InputStream openFile(File configFile) { + try { + if (configFile == null) { + return null; + } else { + return new FileInputStream(configFile); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Could not open config file " + configFile, e); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/support/OrmLiteCursorLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/support/OrmLiteCursorLoader.java new file mode 100644 index 0000000..9bdf300 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/apptools/support/OrmLiteCursorLoader.java @@ -0,0 +1,131 @@ +package com.j256.ormlite.android.apptools.support; + +import static com.j256.ormlite.stmt.StatementBuilder.StatementType.SELECT; + +import java.sql.SQLException; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.content.AsyncTaskLoader; + +import com.j256.ormlite.android.AndroidCompiledStatement; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.Dao.DaoObserver; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.support.DatabaseConnection; + +/** + * Cursor loader supported by later Android APIs that allows asynchronous content loading. + * + *

+ * NOTE: This should be the same as {@link com.j256.ormlite.android.apptools.OrmLiteCursorLoader} but this + * should import the support library version of the {@link AsyncTaskLoader}. + *

+ * + * @author emmby + */ +public class OrmLiteCursorLoader extends AsyncTaskLoader implements DaoObserver { + + protected Dao dao; + protected PreparedQuery query; + protected Cursor cursor; + + public OrmLiteCursorLoader(Context context, Dao dao, PreparedQuery query) { + super(context); + this.dao = dao; + this.query = query; + } + + @Override + public Cursor loadInBackground() { + Cursor cursor; + try { + DatabaseConnection connection = dao.getConnectionSource().getReadOnlyConnection(); + AndroidCompiledStatement statement = (AndroidCompiledStatement) query.compile(connection, SELECT); + cursor = statement.getCursor(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + + // fill the cursor with results + cursor.getCount(); + return cursor; + } + + @Override + public void deliverResult(Cursor newCursor) { + if (isReset()) { + // an async query came in while the loader is stopped + if (newCursor != null) { + newCursor.close(); + } + return; + } + + Cursor oldCursor = cursor; + cursor = newCursor; + + if (isStarted()) { + super.deliverResult(newCursor); + } + + // close the old cursor if necessary + if (oldCursor != null && oldCursor != newCursor && !oldCursor.isClosed()) { + oldCursor.close(); + } + } + + @Override + protected void onStartLoading() { + // start watching for dataset changes + dao.registerObserver(this); + + if (cursor == null) { + forceLoad(); + } else { + deliverResult(cursor); + if (takeContentChanged()) { + forceLoad(); + } + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void onCanceled(Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + @Override + protected void onReset() { + super.onReset(); + onStopLoading(); + if (cursor != null) { + if (!cursor.isClosed()) { + cursor.close(); + } + cursor = null; + } + + // stop watching for changes + dao.unregisterObserver(this); + } + + public void onChange() { + onContentChanged(); + } + + public PreparedQuery getQuery() { + return query; + } + + public void setQuery(PreparedQuery mQuery) { + this.query = mQuery; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibility.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibility.java new file mode 100644 index 0000000..6d76e18 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibility.java @@ -0,0 +1,34 @@ +package com.j256.ormlite.android.compat; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +/** + * Compatibility interface to support various different versions of the Android API. + * + * @author graywatson + */ +public interface ApiCompatibility { + + /** + * Perform a raw query on a database with an optional cancellation-hook. + */ + public Cursor rawQuery(SQLiteDatabase db, String sql, String[] selectionArgs, CancellationHook cancellationHook); + + /** + * Return a cancellation hook object that will be passed to the + * {@link #rawQuery(SQLiteDatabase, String, String[], CancellationHook)}. If not supported then this will return + * null. + */ + public CancellationHook createCancellationHook(); + + /** + * Cancellation hook class returned by {@link ApiCompatibility#createCancellationHook()}. + */ + public interface CancellationHook { + /** + * Cancel the associated query. + */ + public void cancel(); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibilityUtils.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibilityUtils.java new file mode 100644 index 0000000..25686dc --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/ApiCompatibilityUtils.java @@ -0,0 +1,52 @@ +package com.j256.ormlite.android.compat; + +import android.os.Build; + +/** + * Utility class which loads the various classes based on which API version is being supported. + * + * @author graywatson + */ +@SuppressWarnings("unused") +public class ApiCompatibilityUtils { + + private static ApiCompatibility compatibility; + + /** + * Copied from {@link Build.VERSION_CODES}. We don't use those codes because they won't be in certain versions of + * Build. + */ + private static final int BASE = 1; + private static final int BASE_1_1 = 2; + private static final int CUPCAKE = 3; + private static final int DONUT = 4; + private static final int ECLAIR = 5; + private static final int ECLAIR_0_1 = 6; + private static final int ECLAIR_MR1 = 7; + private static final int FROYO = 8; + private static final int GINGERBREAD = 9; + private static final int GINGERBREAD_MR1 = 10; + private static final int HONEYCOMB = 11; + private static final int HONEYCOMB_MR1 = 12; + private static final int HONEYCOMB_MR2 = 13; + private static final int ICE_CREAM_SANDWICH = 14; + private static final int ICE_CREAM_SANDWICH_MR1 = 15; + private static final int JELLY_BEAN = 16; + private static final int JELLY_BEAN_MR1 = 17; + private static final int JELLY_BEAN_MR2 = 18; + + static { + if (Build.VERSION.SDK_INT >= JELLY_BEAN) { + compatibility = new JellyBeanApiCompatibility(); + } else { + compatibility = new BasicApiCompatibility(); + } + } + + /** + * Return the compatibility class that matches our build number. + */ + public static ApiCompatibility getCompatibility() { + return compatibility; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/BasicApiCompatibility.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/BasicApiCompatibility.java new file mode 100644 index 0000000..27be2c7 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/BasicApiCompatibility.java @@ -0,0 +1,21 @@ +package com.j256.ormlite.android.compat; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +/** + * Basic class which provides no-op methods for all Android version. + * + * @author graywatson + */ +public class BasicApiCompatibility implements ApiCompatibility { + + public Cursor rawQuery(SQLiteDatabase db, String sql, String[] selectionArgs, CancellationHook cancellationHook) { + // NOTE: cancellationHook will always be null + return db.rawQuery(sql, selectionArgs); + } + + public CancellationHook createCancellationHook() { + return null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/JellyBeanApiCompatibility.java b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/JellyBeanApiCompatibility.java new file mode 100644 index 0000000..982e7f1 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/android/compat/JellyBeanApiCompatibility.java @@ -0,0 +1,47 @@ +package com.j256.ormlite.android.compat; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.CancellationSignal; + +/** + * Basic class which provides no-op methods for all Android version. + * + *

+ * NOTE: Will show as in error if compiled with previous Android versions. + *

+ * + * @author graywatson + */ +public class JellyBeanApiCompatibility extends BasicApiCompatibility { + + @Override + public Cursor rawQuery(SQLiteDatabase db, String sql, String[] selectionArgs, CancellationHook cancellationHook) { + if (cancellationHook == null) { + return db.rawQuery(sql, selectionArgs); + } else { + return db.rawQuery(sql, selectionArgs, ((JellyBeanCancellationHook) cancellationHook).cancellationSignal); + } + } + + @Override + public CancellationHook createCancellationHook() { + return new JellyBeanCancellationHook(); + } + + /** + * Hook object that supports canceling a running query. + */ + protected static class JellyBeanCancellationHook implements CancellationHook { + + private final CancellationSignal cancellationSignal; + + public JellyBeanCancellationHook() { + this.cancellationSignal = new CancellationSignal(); + } + + public void cancel() { + cancellationSignal.cancel(); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/core/LICENSE.txt b/MemeProject/app/src/main/java/com/j256/ormlite/core/LICENSE.txt new file mode 100644 index 0000000..ff0417e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/core/LICENSE.txt @@ -0,0 +1,17 @@ +Copyright 2010 to 2011 by Gray Watson + +This file is part of the ORMLite package. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +The author may be contacted via http://ormlite.sourceforge.net/ diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/core/README.txt b/MemeProject/app/src/main/java/com/j256/ormlite/core/README.txt new file mode 100644 index 0000000..c7ec0db --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/core/README.txt @@ -0,0 +1,15 @@ +This package provides the core functionality for the JDBC and Android packages. Users +that are connecting to SQL databases via JDBC connections will need to download the +ormlite-jdbc package as well. Android users should download the ormlite-android package +as well as this core package. + +For more information, see the online documentation on the home page: + + http://ormlite.com/ + +Sources can be found online via Github: + + https://github.com/j256/ormlite-core + +Enjoy, +Gray Watson diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseDaoImpl.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseDaoImpl.java new file mode 100644 index 0000000..f25cd6e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseDaoImpl.java @@ -0,0 +1,1081 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.BaseDaoEnabled; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.stmt.DeleteBuilder; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.stmt.PreparedDelete; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.stmt.PreparedUpdate; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.SelectArg; +import com.j256.ormlite.stmt.SelectIterator; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.stmt.StatementExecutor; +import com.j256.ormlite.stmt.UpdateBuilder; +import com.j256.ormlite.stmt.Where; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.DatabaseTableConfig; +import com.j256.ormlite.table.ObjectFactory; +import com.j256.ormlite.table.TableInfo; + +/** + * Base class for the Database Access Objects that handle the reading and writing a class from the database. + * + *

+ * This class is also {@link Iterable} which means you can do a {@code for (T obj : dao)} type of loop code to iterate + * through the table of persisted objects. See {@link #iterator()}. + *

+ * + *

+ * NOTE: If you are using the Spring type wiring, {@link #initialize} should be called after all of the set + * methods. In Spring XML, init-method="initialize" should be used. + *

+ * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public abstract class BaseDaoImpl implements Dao { + + private static final ThreadLocal>> daoConfigLevelLocal = + new ThreadLocal>>() { + @Override + protected List> initialValue() { + return new ArrayList>(10); + } + }; + private static ReferenceObjectCache defaultObjectCache; + private static final Object constantObject = new Object(); + + protected StatementExecutor statementExecutor; + protected DatabaseType databaseType; + protected final Class dataClass; + protected DatabaseTableConfig tableConfig; + protected TableInfo tableInfo; + protected ConnectionSource connectionSource; + protected CloseableIterator lastIterator; + protected ObjectFactory objectFactory; + + private boolean initialized; + private ObjectCache objectCache; + private Map daoObserverMap; + + /** + * Construct our base DAO using Spring type wiring. The {@link ConnectionSource} must be set with the + * {@link #setConnectionSource} method afterwards and then the {@link #initialize()} method must be called. The + * dataClass provided must have its fields marked with {@link DatabaseField} annotations or the + * {@link #setTableConfig} method must be called before the {@link #initialize()} method is called. + * + *

+ * If you are using Spring then your should use: init-method="initialize" + *

+ * + * @param dataClass + * Class associated with this Dao. This must match the T class parameter. + */ + protected BaseDaoImpl(Class dataClass) throws SQLException { + this(null, dataClass, null); + } + + /** + * Construct our base DAO class. The dataClass provided must have its fields marked with {@link DatabaseField} or + * javax.persistance annotations. + * + * @param connectionSource + * Source of our database connections. + * @param dataClass + * Class associated with this Dao. This must match the T class parameter. + */ + protected BaseDaoImpl(ConnectionSource connectionSource, Class dataClass) throws SQLException { + this(connectionSource, dataClass, null); + } + + /** + * Construct our base DAO class. + * + * @param connectionSource + * Source of our database connections. + * @param tableConfig + * Hand or Spring wired table configuration information. + */ + protected BaseDaoImpl(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) throws SQLException { + this(connectionSource, tableConfig.getDataClass(), tableConfig); + } + + private BaseDaoImpl(ConnectionSource connectionSource, Class dataClass, DatabaseTableConfig tableConfig) + throws SQLException { + this.dataClass = dataClass; + this.tableConfig = tableConfig; + if (connectionSource != null) { + this.connectionSource = connectionSource; + initialize(); + } + } + + /** + * Initialize the various DAO configurations after the various setters have been called. + */ + public void initialize() throws SQLException { + if (initialized) { + // just skip it if already initialized + return; + } + + if (connectionSource == null) { + throw new IllegalStateException("connectionSource was never set on " + getClass().getSimpleName()); + } + + databaseType = connectionSource.getDatabaseType(); + if (databaseType == null) { + throw new IllegalStateException("connectionSource is getting a null DatabaseType in " + + getClass().getSimpleName()); + } + if (tableConfig == null) { + tableInfo = new TableInfo(connectionSource, this, dataClass); + } else { + tableConfig.extractFieldTypes(connectionSource); + tableInfo = new TableInfo(databaseType, this, tableConfig); + } + statementExecutor = new StatementExecutor(databaseType, tableInfo, this); + + /* + * This is a bit complex. Initially, when we were configuring the field types, external DAO information would be + * configured for auto-refresh, foreign BaseDaoEnabled classes, and foreign-collections. This would cause the + * system to go recursive and for class loops, a stack overflow. + * + * Then we fixed this by putting a level counter in the FieldType constructor that would stop the configurations + * when we reach some recursion level. But this created some bad problems because we were using the DaoManager + * to cache the created DAOs that had been constructed already limited by the level. + * + * What we do now is have a 2 phase initialization. The constructor initializes most of the fields but then we + * go back and call FieldType.configDaoInformation() after we are done. So for every DAO that is initialized + * here, we have to see if it is the top DAO. If not we save it for dao configuration later. + */ + List> daoConfigList = daoConfigLevelLocal.get(); + daoConfigList.add(this); + if (daoConfigList.size() > 1) { + // if we have recursed then just save the dao for later configuration + return; + } + + try { + /* + * WARNING: We do _not_ use an iterator here because we may be adding to the list as we process it and we'll + * get exceptions otherwise. This is an ArrayList so the get(i) should be efficient. + * + * Also, do _not_ get a copy of daoConfigLevel.doArray because that array may be replaced by another, larger + * one during the recursion. + */ + for (int i = 0; i < daoConfigList.size(); i++) { + BaseDaoImpl dao = daoConfigList.get(i); + + /* + * Here's another complex bit. The first DAO that is being constructed as part of a DAO chain is not yet + * in the DaoManager cache. If we continue onward we might come back around and try to configure this + * DAO again. Forcing it into the cache here makes sure it won't be configured twice. This will cause it + * to be stuck in twice when it returns out to the DaoManager but that's a small price to pay. This also + * applies to self-referencing classes. + */ + DaoManager.registerDao(connectionSource, dao); + + try { + // config our fields which may go recursive + for (FieldType fieldType : dao.getTableInfo().getFieldTypes()) { + fieldType.configDaoInformation(connectionSource, dao.getDataClass()); + } + } catch (SQLException e) { + // unregister the DAO we just pre-registered + DaoManager.unregisterDao(connectionSource, dao); + throw e; + } + + // it's now been fully initialized + dao.initialized = true; + } + } finally { + // if we throw we want to clear our class hierarchy here + daoConfigList.clear(); + daoConfigLevelLocal.remove(); + } + } + + public T queryForId(ID id) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.queryForId(connection, id, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public T queryForFirst(PreparedQuery preparedQuery) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.queryForFirst(connection, preparedQuery, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public List queryForAll() throws SQLException { + checkForInitialized(); + return statementExecutor.queryForAll(connectionSource, objectCache); + } + + public List queryForEq(String fieldName, Object value) throws SQLException { + return queryBuilder().where().eq(fieldName, value).query(); + } + + public QueryBuilder queryBuilder() { + checkForInitialized(); + return new QueryBuilder(databaseType, tableInfo, this); + } + + public UpdateBuilder updateBuilder() { + checkForInitialized(); + return new UpdateBuilder(databaseType, tableInfo, this); + } + + public DeleteBuilder deleteBuilder() { + checkForInitialized(); + return new DeleteBuilder(databaseType, tableInfo, this); + } + + public List query(PreparedQuery preparedQuery) throws SQLException { + checkForInitialized(); + return statementExecutor.query(connectionSource, preparedQuery, objectCache); + } + + public List queryForMatching(T matchObj) throws SQLException { + return queryForMatching(matchObj, false); + } + + public List queryForMatchingArgs(T matchObj) throws SQLException { + return queryForMatching(matchObj, true); + } + + public List queryForFieldValues(Map fieldValues) throws SQLException { + return queryForFieldValues(fieldValues, false); + } + + public List queryForFieldValuesArgs(Map fieldValues) throws SQLException { + return queryForFieldValues(fieldValues, true); + } + + public T queryForSameId(T data) throws SQLException { + checkForInitialized(); + if (data == null) { + return null; + } + ID id = extractId(data); + if (id == null) { + return null; + } else { + return queryForId(id); + } + } + + public int create(T data) throws SQLException { + checkForInitialized(); + // ignore creating a null object + if (data == null) { + return 0; + } + if (data instanceof BaseDaoEnabled) { + @SuppressWarnings("unchecked") + BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data; + daoEnabled.setDao(this); + } + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.create(connection, data, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int create(final Collection datas) throws SQLException { + checkForInitialized(); + for (T data : datas) { + if (data instanceof BaseDaoEnabled) { + @SuppressWarnings("unchecked") + BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data; + daoEnabled.setDao(this); + } + } + /* + * This is a little strange in that we get the connection but then the call-batch-task saves another one. I + * thought that it was an ok thing to do otherwise it made the call-batch-tasks more complicated. + */ + final DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return callBatchTasks(new Callable() { + public Integer call() throws SQLException { + int modCount = 0; + for (T data : datas) { + modCount += statementExecutor.create(connection, data, objectCache); + } + return modCount; + } + }); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public T createIfNotExists(T data) throws SQLException { + if (data == null) { + return null; + } + T existing = queryForSameId(data); + if (existing == null) { + create(data); + return data; + } else { + return existing; + } + } + + public CreateOrUpdateStatus createOrUpdate(T data) throws SQLException { + if (data == null) { + return new CreateOrUpdateStatus(false, false, 0); + } + ID id = extractId(data); + // assume we need to create it if there is no id + if (id == null || !idExists(id)) { + int numRows = create(data); + return new CreateOrUpdateStatus(true, false, numRows); + } else { + int numRows = update(data); + return new CreateOrUpdateStatus(false, true, numRows); + } + } + + public int update(T data) throws SQLException { + checkForInitialized(); + // ignore updating a null object + if (data == null) { + return 0; + } + if (data instanceof BaseDaoEnabled) { + @SuppressWarnings("unchecked") + BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data; + daoEnabled.setDao(this); + } + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.update(connection, data, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int updateId(T data, ID newId) throws SQLException { + checkForInitialized(); + // ignore updating a null object + if (data == null) { + return 0; + } else { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.updateId(connection, data, newId, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + } + + public int update(PreparedUpdate preparedUpdate) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.update(connection, preparedUpdate); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int refresh(T data) throws SQLException { + checkForInitialized(); + // ignore refreshing a null object + if (data == null) { + return 0; + } + if (data instanceof BaseDaoEnabled) { + @SuppressWarnings("unchecked") + BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data; + daoEnabled.setDao(this); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.refresh(connection, data, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int delete(T data) throws SQLException { + checkForInitialized(); + // ignore deleting a null object + if (data == null) { + return 0; + } else { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.delete(connection, data, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + } + + public int deleteById(ID id) throws SQLException { + checkForInitialized(); + // ignore deleting a null id + if (id == null) { + return 0; + } else { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.deleteById(connection, id, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + } + + public int delete(Collection datas) throws SQLException { + checkForInitialized(); + // ignore deleting a null object + if (datas == null || datas.isEmpty()) { + return 0; + } else { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.deleteObjects(connection, datas, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + } + + public int deleteIds(Collection ids) throws SQLException { + checkForInitialized(); + // ignore deleting a null object + if (ids == null || ids.isEmpty()) { + return 0; + } else { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.deleteIds(connection, ids, objectCache); + } finally { + connectionSource.releaseConnection(connection); + } + } + } + + public int delete(PreparedDelete preparedDelete) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.delete(connection, preparedDelete); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public CloseableIterator iterator() { + return iterator(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator closeableIterator() { + return iterator(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iterator(int resultFlags) { + checkForInitialized(); + lastIterator = createIterator(resultFlags); + return lastIterator; + } + + public CloseableWrappedIterable getWrappedIterable() { + checkForInitialized(); + return new CloseableWrappedIterableImpl(new CloseableIterable() { + public Iterator iterator() { + return closeableIterator(); + } + public CloseableIterator closeableIterator() { + try { + return BaseDaoImpl.this.createIterator(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } catch (Exception e) { + throw new IllegalStateException("Could not build iterator for " + dataClass, e); + } + } + }); + } + + public CloseableWrappedIterable getWrappedIterable(final PreparedQuery preparedQuery) { + checkForInitialized(); + return new CloseableWrappedIterableImpl(new CloseableIterable() { + public Iterator iterator() { + return closeableIterator(); + } + public CloseableIterator closeableIterator() { + try { + return BaseDaoImpl.this.createIterator(preparedQuery, DatabaseConnection.DEFAULT_RESULT_FLAGS); + } catch (Exception e) { + throw new IllegalStateException("Could not build prepared-query iterator for " + dataClass, e); + } + } + }); + } + + public void closeLastIterator() throws IOException { + if (lastIterator != null) { + lastIterator.close(); + lastIterator = null; + } + } + + public CloseableIterator iterator(PreparedQuery preparedQuery) throws SQLException { + return iterator(preparedQuery, DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iterator(PreparedQuery preparedQuery, int resultFlags) throws SQLException { + checkForInitialized(); + lastIterator = createIterator(preparedQuery, resultFlags); + return lastIterator; + } + + public GenericRawResults queryRaw(String query, String... arguments) throws SQLException { + checkForInitialized(); + try { + return statementExecutor.queryRaw(connectionSource, query, arguments, objectCache); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not perform raw query for " + query, e); + } + } + + public GenericRawResults queryRaw(String query, RawRowMapper mapper, String... arguments) + throws SQLException { + checkForInitialized(); + try { + return statementExecutor.queryRaw(connectionSource, query, mapper, arguments, objectCache); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not perform raw query for " + query, e); + } + } + + public GenericRawResults queryRaw(String query, DataType[] columnTypes, RawRowObjectMapper mapper, + String... arguments) throws SQLException { + checkForInitialized(); + try { + return statementExecutor.queryRaw(connectionSource, query, columnTypes, mapper, arguments, objectCache); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not perform raw query for " + query, e); + } + } + + public GenericRawResults queryRaw(String query, DataType[] columnTypes, String... arguments) + throws SQLException { + checkForInitialized(); + try { + return statementExecutor.queryRaw(connectionSource, query, columnTypes, arguments, objectCache); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not perform raw query for " + query, e); + } + } + + public GenericRawResults queryRaw(String query, DatabaseResultsMapper mapper, String... arguments) + throws SQLException { + checkForInitialized(); + try { + return statementExecutor.queryRaw(connectionSource, query, mapper, arguments, objectCache); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not perform raw query for " + query, e); + } + } + + public long queryRawValue(String query, String... arguments) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.queryForLong(connection, query, arguments); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not perform raw value query for " + query, e); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int executeRaw(String statement, String... arguments) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.executeRaw(connection, statement, arguments); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not run raw execute statement " + statement, e); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int executeRawNoArgs(String statement) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.executeRawNoArgs(connection, statement); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not run raw execute statement " + statement, e); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public int updateRaw(String statement, String... arguments) throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return statementExecutor.updateRaw(connection, statement, arguments); + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not run raw update statement " + statement, e); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public CT callBatchTasks(Callable callable) throws SQLException { + checkForInitialized(); + return statementExecutor.callBatchTasks(connectionSource, callable); + } + + public String objectToString(T data) { + checkForInitialized(); + return tableInfo.objectToString(data); + } + + public boolean objectsEqual(T data1, T data2) throws SQLException { + checkForInitialized(); + for (FieldType fieldType : tableInfo.getFieldTypes()) { + Object fieldObj1 = fieldType.extractJavaFieldValue(data1); + Object fieldObj2 = fieldType.extractJavaFieldValue(data2); + // we can't just do fieldObj1.equals(fieldObj2) because of byte[].equals() + if (!fieldType.getDataPersister().dataIsEqual(fieldObj1, fieldObj2)) { + return false; + } + } + return true; + } + + public ID extractId(T data) throws SQLException { + checkForInitialized(); + FieldType idField = tableInfo.getIdField(); + if (idField == null) { + throw new SQLException("Class " + dataClass + " does not have an id field"); + } + @SuppressWarnings("unchecked") + ID id = (ID) idField.extractJavaFieldValue(data); + return id; + } + + public Class getDataClass() { + return dataClass; + } + + public FieldType findForeignFieldType(Class clazz) { + checkForInitialized(); + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (fieldType.getType() == clazz) { + return fieldType; + } + } + return null; + } + + public boolean isUpdatable() { + return tableInfo.isUpdatable(); + } + + public boolean isTableExists() throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return connection.isTableExists(tableInfo.getTableName()); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public long countOf() throws SQLException { + checkForInitialized(); + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.queryForCountStar(connection); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public long countOf(PreparedQuery preparedQuery) throws SQLException { + checkForInitialized(); + if (preparedQuery.getType() != StatementType.SELECT_LONG) { + throw new IllegalArgumentException("Prepared query is not of type " + StatementType.SELECT_LONG + + ", you need to call QueryBuilder.setCountOf(true)"); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.queryForLong(connection, preparedQuery); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public void assignEmptyForeignCollection(T parent, String fieldName) throws SQLException { + makeEmptyForeignCollection(parent, fieldName); + } + + public ForeignCollection getEmptyForeignCollection(String fieldName) throws SQLException { + return makeEmptyForeignCollection(null, fieldName); + } + + public void setObjectCache(boolean enabled) throws SQLException { + if (enabled) { + if (objectCache == null) { + if (tableInfo.getIdField() == null) { + throw new SQLException("Class " + dataClass + " must have an id field to enable the object cache"); + } + synchronized (BaseDaoImpl.class) { + if (defaultObjectCache == null) { + // only make one + defaultObjectCache = ReferenceObjectCache.makeWeakCache(); + } + objectCache = defaultObjectCache; + } + objectCache.registerClass(dataClass); + } + } else { + if (objectCache != null) { + objectCache.clear(dataClass); + objectCache = null; + } + } + } + + public void setObjectCache(ObjectCache objectCache) throws SQLException { + if (objectCache == null) { + if (this.objectCache != null) { + // help with GC-ing + this.objectCache.clear(dataClass); + this.objectCache = null; + } + } else { + if (this.objectCache != null && this.objectCache != objectCache) { + // help with GC-ing + this.objectCache.clear(dataClass); + } + if (tableInfo.getIdField() == null) { + throw new SQLException("Class " + dataClass + " must have an id field to enable the object cache"); + } + this.objectCache = objectCache; + this.objectCache.registerClass(dataClass); + } + } + + public ObjectCache getObjectCache() { + return objectCache; + } + + public void clearObjectCache() { + if (objectCache != null) { + objectCache.clear(dataClass); + } + } + + /** + * Special call mostly used in testing to clear the internal object caches so we can reset state. + */ + public synchronized static void clearAllInternalObjectCaches() { + if (defaultObjectCache != null) { + defaultObjectCache.clearAll(); + defaultObjectCache = null; + } + } + + public T mapSelectStarRow(DatabaseResults results) throws SQLException { + return statementExecutor.getSelectStarRowMapper().mapRow(results); + } + + public void notifyChanges() { + if (daoObserverMap != null) { + for (DaoObserver daoObserver : daoObserverMap.keySet()) { + daoObserver.onChange(); + } + } + } + + public void registerObserver(DaoObserver observer) { + if (daoObserverMap == null) { + /* + * We are doing this so no other systems need to pay the penalty for the new observer code. This seems like + * it would be a double-check locking bug but I don't think so. We are putting into a synchronized + * collection that locks so we should not have partial constructor issues. + */ + synchronized (this) { + if (daoObserverMap == null) { + daoObserverMap = new ConcurrentHashMap(); + } + } + } + // no values in the map + daoObserverMap.put(observer, constantObject); + } + + public void unregisterObserver(DaoObserver observer) { + if (daoObserverMap != null) { + synchronized (daoObserverMap) { + daoObserverMap.remove(observer); + } + } + } + + public GenericRowMapper getSelectStarRowMapper() throws SQLException { + return statementExecutor.getSelectStarRowMapper(); + } + + public RawRowMapper getRawRowMapper() { + return statementExecutor.getRawRowMapper(); + } + + public boolean idExists(ID id) throws SQLException { + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + try { + return statementExecutor.ifExists(connection, id); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public DatabaseConnection startThreadConnection() throws SQLException { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + connectionSource.saveSpecialConnection(connection); + return connection; + } + + public void endThreadConnection(DatabaseConnection connection) throws SQLException { + connectionSource.clearSpecialConnection(connection); + connectionSource.releaseConnection(connection); + } + + public void setAutoCommit(boolean autoCommit) throws SQLException { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + setAutoCommit(connection, autoCommit); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public void setAutoCommit(DatabaseConnection connection, boolean autoCommit) throws SQLException { + connection.setAutoCommit(autoCommit); + } + + public boolean isAutoCommit() throws SQLException { + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return isAutoCommit(connection); + } finally { + connectionSource.releaseConnection(connection); + } + } + + public boolean isAutoCommit(DatabaseConnection connection) throws SQLException { + return connection.isAutoCommit(); + } + + public void commit(DatabaseConnection connection) throws SQLException { + connection.commit(null); + } + + public void rollBack(DatabaseConnection connection) throws SQLException { + connection.rollback(null); + } + + public ObjectFactory getObjectFactory() { + return objectFactory; + } + + public void setObjectFactory(ObjectFactory objectFactory) { + checkForInitialized(); + this.objectFactory = objectFactory; + } + + /** + * Returns the table configuration information associated with the Dao's class or null if none. + */ + public DatabaseTableConfig getTableConfig() { + return tableConfig; + } + + /** + * Used by internal classes to get the table information structure for the Dao's class. + */ + public TableInfo getTableInfo() { + return tableInfo; + } + + public ConnectionSource getConnectionSource() { + return connectionSource; + } + + public void setConnectionSource(ConnectionSource connectionSource) { + this.connectionSource = connectionSource; + } + + /** + * Used if you want to configure the class for the Dao by hand or with spring instead of using the + * {@link DatabaseField} annotation in the class. This must be called before {@link #initialize}. + */ + public void setTableConfig(DatabaseTableConfig tableConfig) { + this.tableConfig = tableConfig; + } + + /** + * Helper method to create a Dao object without having to define a class. Dao classes are supposed to be convenient + * but if you have a lot of classes, they can seem to be a pain. + * + *

+ * NOTE: You should use {@link DaoManager#createDao(ConnectionSource, DatabaseTableConfig)} instead of this + * method so you won't have to create the DAO multiple times. + *

+ */ + static Dao createDao(ConnectionSource connectionSource, Class clazz) throws SQLException { + return new BaseDaoImpl(connectionSource, clazz) { + }; + } + + /** + * Helper method to create a Dao object used by some internal methods that already have the {@link TableInfo}. + * + *

+ * NOTE: You should use {@link DaoManager#createDao(ConnectionSource, DatabaseTableConfig)} instead of this + * method so you won't have to create the DAO multiple times. + *

+ */ + static Dao createDao(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) + throws SQLException { + return new BaseDaoImpl(connectionSource, tableConfig) { + }; + } + + protected void checkForInitialized() { + if (!initialized) { + throw new IllegalStateException("you must call initialize() before you can use the dao"); + } + } + + private ForeignCollection makeEmptyForeignCollection(T parent, String fieldName) throws SQLException { + checkForInitialized(); + ID id; + if (parent == null) { + id = null; + } else { + id = extractId(parent); + } + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (fieldType.getColumnName().equals(fieldName)) { + @SuppressWarnings("unchecked") + ForeignCollection collection = (ForeignCollection) fieldType.buildForeignCollection(parent, id); + if (parent != null) { + fieldType.assignField(parent, collection, true, null); + } + return collection; + } + } + throw new IllegalArgumentException("Could not find a field named " + fieldName); + } + + private CloseableIterator createIterator(int resultFlags) { + try { + SelectIterator iterator = + statementExecutor.buildIterator(this, connectionSource, resultFlags, objectCache); + return iterator; + } catch (Exception e) { + throw new IllegalStateException("Could not build iterator for " + dataClass, e); + } + } + + private CloseableIterator createIterator(PreparedQuery preparedQuery, int resultFlags) throws SQLException { + try { + SelectIterator iterator = + statementExecutor.buildIterator(this, connectionSource, preparedQuery, objectCache, resultFlags); + return iterator; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Could not build prepared-query iterator for " + dataClass, e); + } + } + + private List queryForMatching(T matchObj, boolean useArgs) throws SQLException { + checkForInitialized(); + QueryBuilder qb = queryBuilder(); + Where where = qb.where(); + int fieldC = 0; + for (FieldType fieldType : tableInfo.getFieldTypes()) { + Object fieldValue = fieldType.getFieldValueIfNotDefault(matchObj); + if (fieldValue != null) { + if (useArgs) { + fieldValue = new SelectArg(fieldValue); + } + where.eq(fieldType.getColumnName(), fieldValue); + fieldC++; + } + } + if (fieldC == 0) { + return Collections.emptyList(); + } else { + where.and(fieldC); + return qb.query(); + } + } + + private List queryForFieldValues(Map fieldValues, boolean useArgs) throws SQLException { + checkForInitialized(); + QueryBuilder qb = queryBuilder(); + Where where = qb.where(); + for (Map.Entry entry : fieldValues.entrySet()) { + Object fieldValue = entry.getValue(); + if (useArgs) { + fieldValue = new SelectArg(fieldValue); + } + where.eq(entry.getKey(), fieldValue); + } + if (fieldValues.size() == 0) { + return Collections.emptyList(); + } else { + where.and(fieldValues.size()); + return qb.query(); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseForeignCollection.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseForeignCollection.java new file mode 100644 index 0000000..b8ec8b0 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/BaseForeignCollection.java @@ -0,0 +1,195 @@ +package com.j256.ormlite.dao; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.Collection; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.SelectArg; +import com.j256.ormlite.stmt.mapped.MappedPreparedStmt; + +/** + * Base collection that is set on a field that as been marked with the {@link ForeignCollectionField} annotation when an + * object is refreshed or queried (i.e. not created). + * + *

+ * WARNING: Most likely for(;;) loops should not be used here since we need to be careful about closing the + * iterator. + *

+ * + * @author graywatson + */ +public abstract class BaseForeignCollection implements ForeignCollection, Serializable { + + private static final long serialVersionUID = -5158840898186237589L; + + protected transient final Dao dao; + private transient final FieldType foreignFieldType; + private transient final Object parentId; + private transient PreparedQuery preparedQuery; + private transient final String orderColumn; + private transient final boolean orderAscending; + private transient final Object parent; + + protected BaseForeignCollection(Dao dao, Object parent, Object parentId, FieldType foreignFieldType, + String orderColumn, boolean orderAscending) { + this.dao = dao; + this.foreignFieldType = foreignFieldType; + this.parentId = parentId; + this.orderColumn = orderColumn; + this.orderAscending = orderAscending; + this.parent = parent; + } + + /** + * Add an element to the collection. This will also add the item to the associated database table. + * + * @return Returns true if the item did not already exist in the collection otherwise false. + */ + public boolean add(T data) { + try { + return addElement(data); + } catch (SQLException e) { + throw new IllegalStateException("Could not create data element in dao", e); + } + } + + /** + * Add the collection of elements to this collection. This will also them to the associated database table. + * + * @return Returns true if any of the items did not already exist in the collection otherwise false. + */ + public boolean addAll(Collection collection) { + boolean changed = false; + for (T data : collection) { + try { + if (addElement(data)) { + changed = true; + } + } catch (SQLException e) { + throw new IllegalStateException("Could not create data elements in dao", e); + } + } + return changed; + } + + /** + * Remove the item from the collection and the associated database table. + * + * NOTE: we can't just do a dao.delete(data) because it has to be in the collection. + * + * @return True if the item was found in the collection otherwise false. + */ + public abstract boolean remove(Object data); + + /** + * Remove the items in the collection argument from the foreign collection and the associated database table. + * + * NOTE: we can't just do a for (...) dao.delete(item) because the items have to be in the collection. + * + * @return True if the item was found in the collection otherwise false. + */ + public abstract boolean removeAll(Collection collection); + + /** + * Uses the iterator to run through the dao and retain only the items that are in the passed in collection. This + * will remove the items from the associated database table as well. + * + * @return Returns true of the collection was changed at all otherwise false. + */ + public boolean retainAll(Collection collection) { + if (dao == null) { + return false; + } + boolean changed = false; + CloseableIterator iterator = closeableIterator(); + try { + while (iterator.hasNext()) { + T data = iterator.next(); + if (!collection.contains(data)) { + iterator.remove(); + changed = true; + } + } + return changed; + } finally { + IOUtils.closeQuietly(iterator); + } + } + + /** + * Clears the collection and uses the iterator to run through the dao and delete all of the items in the collection + * from the associated database table. This is different from removing all of the elements in the table since this + * iterator is across just one item's foreign objects. + */ + public void clear() { + if (dao == null) { + return; + } + CloseableIterator iterator = closeableIterator(); + try { + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } finally { + IOUtils.closeQuietly(iterator); + } + } + + public int update(T data) throws SQLException { + if (dao == null) { + return 0; + } else { + return dao.update(data); + } + } + + public int refresh(T data) throws SQLException { + if (dao == null) { + return 0; + } else { + return dao.refresh(data); + } + } + + public Dao getDao() { + return dao; + } + + protected PreparedQuery getPreparedQuery() throws SQLException { + if (dao == null) { + return null; + } + if (preparedQuery == null) { + SelectArg fieldArg = new SelectArg(); + fieldArg.setValue(parentId); + QueryBuilder qb = dao.queryBuilder(); + if (orderColumn != null) { + qb.orderBy(orderColumn, orderAscending); + } + preparedQuery = qb.where().eq(foreignFieldType.getColumnName(), fieldArg).prepare(); + if (preparedQuery instanceof MappedPreparedStmt) { + @SuppressWarnings("unchecked") + MappedPreparedStmt mappedStmt = ((MappedPreparedStmt) preparedQuery); + mappedStmt.setParentInformation(parent, parentId); + } + } + return preparedQuery; + } + + private boolean addElement(T data) throws SQLException { + if (dao == null) { + return false; + } + if (parent != null && foreignFieldType.getFieldValueIfNotDefault(data) == null) { + foreignFieldType.assignField(data, parent, true, null); + } + dao.create(data); + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterable.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterable.java new file mode 100644 index 0000000..224bdf2 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterable.java @@ -0,0 +1,14 @@ +package com.j256.ormlite.dao; + +/** + * Extension to Iterable to provide a iterator() method that returns a {@link CloseableIterator}. + * + * @author graywatson + */ +public interface CloseableIterable extends Iterable { + + /** + * Returns an iterator over a set of elements of type T which can be closed. + */ + public CloseableIterator closeableIterator(); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterator.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterator.java new file mode 100644 index 0000000..cb4893c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableIterator.java @@ -0,0 +1,73 @@ +package com.j256.ormlite.dao; + +import java.io.Closeable; +import java.sql.SQLException; +import java.util.Iterator; + +import com.j256.ormlite.support.DatabaseResults; + +/** + * Extension to Iterator to provide a close() method. This should be in the JDK. + * + *

+ * NOTE: You must call {@link CloseableIterator#close()} method when you are done otherwise the underlying SQL + * statement and connection may be kept open. + *

+ * + * @author graywatson + */ +public interface CloseableIterator extends Iterator, Closeable { + + /** + * Close any underlying SQL statements but swallow any SQLExceptions. + */ + public void closeQuietly(); + + /** + * Return the underlying database results object if any. May return null. This should not be used unless you know + * what you are doing. + */ + public DatabaseResults getRawResults(); + + /** + * Move to the next item in the iterator without calling {@link #next()}. + */ + public void moveToNext(); + + /** + * Move to the first result and return it or null if none. This may not work with the default iterator depending on + * your database. + */ + public T first() throws SQLException; + + /** + * Moves to the previous result and return it or null if none. This may not work with the default iterator depending + * on your database. + */ + public T previous() throws SQLException; + + /** + * Return the current result object that we have accessed or null if none. + */ + public T current() throws SQLException; + + /** + * Returns the {@link #next()} object in the table or null if none. + * + * @throws SQLException + * Throws a SQLException on error since {@link #next()} cannot throw because it is part of the + * {@link Iterator} definition. It will not throw if there is no next. + */ + public T nextThrow() throws SQLException; + + /** + * Move a relative position in the list and return that result or null if none. Moves forward (positive value) or + * backwards (negative value) the list of results. moveRelative(1) should be the same as {@link #next()}. + * moveRelative(-1) is the same as {@link #previous} result. This may not work with the default iterator depending + * on your database. + * + * @param offset + * Number of rows to move. Positive moves forward in the results. Negative moves backwards. + */ + public T moveRelative(int offset) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterable.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterable.java new file mode 100644 index 0000000..dba6710 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterable.java @@ -0,0 +1,29 @@ +package com.j256.ormlite.dao; + +import java.io.Closeable; +import java.io.IOException; + +/** + * Extension to CloseableIterable which defines a class which has an iterator() method that returns a + * {@link CloseableIterator} but also can be closed itself. This allows us to do something like this pattern: + * + *
+ * CloseableWrappedIterable<Foo> wrapperIterable = fooDao.getCloseableIterable();
+ * try {
+ *   for (Foo foo : wrapperIterable) {
+ *       ...
+ *   }
+ * } finally {
+ *   wrapperIterable.close();
+ * }
+ * 
+ * + * @author graywatson + */ +public interface CloseableWrappedIterable extends CloseableIterable, Closeable { + + /** + * This will close the last iterator returned by the {@link #iterator()} method. + */ + public void close() throws IOException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterableImpl.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterableImpl.java new file mode 100644 index 0000000..a589abd --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/CloseableWrappedIterableImpl.java @@ -0,0 +1,39 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; + +import com.j256.ormlite.misc.IOUtils; + +/** + * Class which is used to help folks use for loops but still close at the end. This is a wrapper to allow multiple + * threads to iterate across the same dao or the same lazy collection at the same time. See + * {@link Dao#getWrappedIterable()} or {@link ForeignCollection#getWrappedIterable()}. + * + * @author graywatson + */ +public class CloseableWrappedIterableImpl implements CloseableWrappedIterable { + + private final CloseableIterable iterable; + private CloseableIterator iterator; + + public CloseableWrappedIterableImpl(CloseableIterable iterable) { + this.iterable = iterable; + } + + public CloseableIterator iterator() { + return closeableIterator(); + } + + public CloseableIterator closeableIterator() { + IOUtils.closeQuietly(this); + iterator = iterable.closeableIterator(); + return iterator; + } + + public void close() throws IOException { + if (iterator != null) { + iterator.close(); + iterator = null; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/Dao.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/Dao.java new file mode 100644 index 0000000..0a6d0fb --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/Dao.java @@ -0,0 +1,891 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.stmt.DeleteBuilder; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.stmt.PreparedDelete; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.stmt.PreparedUpdate; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.SelectArg; +import com.j256.ormlite.stmt.UpdateBuilder; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.ObjectFactory; + +/** + * The definition of the Database Access Objects that handle the reading and writing a class from the database. Kudos to + * Robert A. for the general concept of this hierarchy. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public interface Dao extends CloseableIterable { + + /** + * Retrieves an object associated with a specific ID. + * + * @param id + * Identifier that matches a specific row in the database to find and return. + * @return The object that has the ID field which equals id or null if no matches. + * @throws SQLException + * on any SQL problems or if more than 1 item with the id are found in the database. + */ + public T queryForId(ID id) throws SQLException; + + /** + * Query for and return the first item in the object table which matches the PreparedQuery. See + * {@link #queryBuilder()} for more information. This can be used to return the object that matches a single unique + * column. You should use {@link #queryForId(Object)} if you want to query for the id column. + * + * @param preparedQuery + * Query used to match the objects in the database. + * @return The first object that matches the query. + * @throws SQLException + * on any SQL problems. + */ + public T queryForFirst(PreparedQuery preparedQuery) throws SQLException; + + /** + * Query for all of the items in the object table. For medium sized or large tables, this may load a lot of objects + * into memory so you should consider using the {@link #iterator()} method instead. + * + * @return A list of all of the objects in the table. + * @throws SQLException + * on any SQL problems. + */ + public List queryForAll() throws SQLException; + + /** + * Query for the items in the object table that match a simple where with a single field = value type of WHERE + * clause. This is a convenience method for calling queryBuilder().where().eq(fieldName, value).query(). + * + * @return A list of the objects in the table that match the fieldName = value; + * @throws SQLException + * on any SQL problems. + */ + public List queryForEq(String fieldName, Object value) throws SQLException; + + /** + * Query for the rows in the database that match the object passed in as a parameter. Any fields in the matching + * object that are not the default value (null, false, 0, 0.0, etc.) are used as the matching parameters with AND. + * If you are worried about SQL quote escaping, you should use {@link #queryForMatchingArgs(Object)}. + */ + public List queryForMatching(T matchObj) throws SQLException; + + /** + * Same as {@link #queryForMatching(Object)} but this uses {@link SelectArg} and SQL ? arguments. This is slightly + * more expensive but you don't have to worry about SQL quote escaping. + */ + public List queryForMatchingArgs(T matchObj) throws SQLException; + + /** + * Query for the rows in the database that matches all of the field to value entries from the map passed in. If you + * are worried about SQL quote escaping, you should use {@link #queryForFieldValuesArgs(Map)}. + */ + public List queryForFieldValues(Map fieldValues) throws SQLException; + + /** + * Same as {@link #queryForFieldValues(Map)} but this uses {@link SelectArg} and SQL ? arguments. This is slightly + * more expensive but you don't have to worry about SQL quote escaping. + */ + public List queryForFieldValuesArgs(Map fieldValues) throws SQLException; + + /** + * Query for a data item in the table that has the same id as the data parameter. + */ + public T queryForSameId(T data) throws SQLException; + + /** + * Create and return a new query builder object which allows you to build a custom SELECT statement. You call + * methods on the builder to construct your statement and then call {@link QueryBuilder#prepare()} once you are + * ready to build. This returns a {@link PreparedQuery} object which gets passed to {@link #query(PreparedQuery)} or + * {@link #iterator(PreparedQuery)}. + */ + public QueryBuilder queryBuilder(); + + /** + * Like {@link #queryBuilder()} but allows you to build an UPDATE statement. You can then call call + * {@link UpdateBuilder#prepare()} and pass the returned {@link PreparedUpdate} to {@link #update(PreparedUpdate)}. + */ + public UpdateBuilder updateBuilder(); + + /** + * Like {@link #queryBuilder()} but allows you to build an DELETE statement. You can then call call + * {@link DeleteBuilder#prepare()} and pass the returned {@link PreparedDelete} to {@link #delete(PreparedDelete)}. + */ + public DeleteBuilder deleteBuilder(); + + /** + * Query for the items in the object table which match the prepared query. See {@link #queryBuilder} for more + * information. + * + *

+ * NOTE: For medium sized or large tables, this may load a lot of objects into memory so you should consider + * using the {@link #iterator(PreparedQuery)} method instead. + *

+ * + * @param preparedQuery + * Query used to match the objects in the database. + * @return A list of all of the objects in the table that match the query. + * @throws SQLException + * on any SQL problems. + */ + public List query(PreparedQuery preparedQuery) throws SQLException; + + /** + * Create a new row in the database from an object. If the object being created uses + * {@link DatabaseField#generatedId()} then the data parameter will be modified and set with the corresponding id + * from the database. + * + * @param data + * The data item that we are creating in the database. + * @return The number of rows updated in the database. This should be 1. + */ + public int create(T data) throws SQLException; + + /** + * Just like {@link #create(Object)} but with a collection of objects. This will wrap the creates using the same + * mechanism as {@link #callBatchTasks(Callable)}. + * + * @param datas + * The collection of data items that we are creating in the database. + * @return The number of rows updated in the database. + */ + public int create(Collection datas) throws SQLException; + + /** + * This is a convenience method to creating a data item but only if the ID does not already exist in the table. This + * extracts the id from the data parameter, does a {@link #queryForId(Object)} on it, returning the data if it + * exists. If it does not exist {@link #create(Object)} will be called with the parameter. + * + * @return Either the data parameter if it was inserted (now with the ID field set via the create method) or the + * data element that existed already in the database. + */ + public T createIfNotExists(T data) throws SQLException; + + /** + * This is a convenience method for creating an item in the database if it does not exist. The id is extracted from + * the data parameter and a query-by-id is made on the database. If a row in the database with the same id exists + * then all of the columns in the database will be updated from the fields in the data parameter. If the id is null + * (or 0 or some other default value) or doesn't exist in the database then the object will be created in the + * database. This also means that your data item must have an id field defined. + * + * @return Status object with the number of rows changed and whether an insert or update was performed. + */ + public CreateOrUpdateStatus createOrUpdate(T data) throws SQLException; + + /** + * Store the fields from an object to the database row corresponding to the id from the data parameter. If you have + * made changes to an object, this is how you persist those changes to the database. You cannot use this method to + * update the id field -- see {@link #updateId} . + * + *

+ * NOTE: This will not save changes made to foreign objects or to foreign collections. + *

+ * + * @param data + * The data item that we are updating in the database. + * @return The number of rows updated in the database. This should be 1. + * @throws SQLException + * on any SQL problems. + * @throws IllegalArgumentException + * If there is only an ID field in the object. See the {@link #updateId} method. + */ + public int update(T data) throws SQLException; + + /** + * Update the data parameter in the database to change its id to the newId parameter. The data must have its + * current (old) id set. If the id field has already changed then it cannot be updated. After the id has been + * updated in the database, the id field of the data parameter will also be changed. + * + *

+ * NOTE: Depending on the database type and the id type, you may be unable to change the id of the field. + *

+ * + * @param data + * The data item that we are updating in the database with the current id. + * @param newId + * The new id that you want to update the data with. + * @return The number of rows updated in the database. This should be 1. + * @throws SQLException + * on any SQL problems. + */ + public int updateId(T data, ID newId) throws SQLException; + + /** + * Update all rows in the table according to the prepared statement parameter. To use this, the + * {@link UpdateBuilder} must have set-columns applied to it using the + * {@link UpdateBuilder#updateColumnValue(String, Object)} or + * {@link UpdateBuilder#updateColumnExpression(String, String)} methods. + * + * @param preparedUpdate + * A prepared statement to match database rows to be deleted and define the columns to update. + * @return The number of rows updated in the database. + * @throws SQLException + * on any SQL problems. + * @throws IllegalArgumentException + * If there is only an ID field in the object. See the {@link #updateId} method. + */ + public int update(PreparedUpdate preparedUpdate) throws SQLException; + + /** + * Does a query for the data parameter's id and copies in each of the field values from the database to refresh the + * data parameter. Any local object changes to persisted fields will be overwritten. If the database has been + * updated this brings your local object up to date. + * + * @param data + * The data item that we are refreshing with fields from the database. + * @return The number of rows found in the database that correspond to the data id. This should be 1. + * @throws SQLException + * on any SQL problems or if the data item is not found in the table or if more than 1 item is found + * with data's id. + */ + public int refresh(T data) throws SQLException; + + /** + * Delete the database row corresponding to the id from the data parameter. + * + * @param data + * The data item that we are deleting from the database. + * @return The number of rows updated in the database. This should be 1. + * @throws SQLException + * on any SQL problems. + */ + public int delete(T data) throws SQLException; + + /** + * Delete an object from the database that has an id. + * + * @param id + * The id of the item that we are deleting from the database. + * @return The number of rows updated in the database. This should be 1. + * @throws SQLException + * on any SQL problems. + */ + public int deleteById(ID id) throws SQLException; + + /** + * Delete a collection of objects from the database using an IN SQL clause. The ids are extracted from the datas + * parameter and used to remove the corresponding rows in the database with those ids. + * + * @param datas + * A collection of data items to be deleted. + * @return The number of rows updated in the database. This should be the size() of the collection. + * @throws SQLException + * on any SQL problems. + */ + public int delete(Collection datas) throws SQLException; + + /** + * Delete the objects that match the collection of ids from the database using an IN SQL clause. + * + * @param ids + * A collection of data ids to be deleted. + * @return The number of rows updated in the database. This should be the size() of the collection. + * @throws SQLException + * on any SQL problems. + */ + public int deleteIds(Collection ids) throws SQLException; + + /** + * Delete the objects that match the prepared statement parameter. + * + * @param preparedDelete + * A prepared statement to match database rows to be deleted. + * @return The number of rows updated in the database. + * @throws SQLException + * on any SQL problems. + */ + public int delete(PreparedDelete preparedDelete) throws SQLException; + + /** + * This satisfies the {@link Iterable} interface for the class and allows you to iterate through the objects in the + * table using SQL. You can use code similar to the following: + * + *
+	 * for (Account account : accountDao) { ... }
+	 * 
+ * + *

+ * WARNING: because the {@link Iterator#hasNext()}, {@link Iterator#next()}, etc. methods can only throw + * {@link RuntimeException}, the code has to wrap any {@link SQLException} with {@link IllegalStateException}. Make + * sure to catch {@link IllegalStateException} and look for a {@link SQLException} cause. + *

+ * + *

+ * WARNING: The underlying results object will only be closed if you page all the way to the end of the + * iterator using the for() loop or if you call {@link CloseableIterator#close()} directly. You can also call the + * {@link #closeLastIterator()} if you are not iterating across this DAO in multiple threads. + *

+ * + *

+ * NOTE: With this iterator you can only move forward through the object collection. See the + * {@link #iterator(int)} method to create a cursor that can go both directions. + *

+ * + * @return An iterator of the class that uses SQL to step across the database table. + * + * @throws IllegalStateException + * When it encounters a SQLException or in other cases. + */ + public CloseableIterator iterator(); + + /** + * Same as {@link #iterator()} but while specifying flags for the results. This is necessary with certain database + * types. The resultFlags could be something like ResultSet.TYPE_SCROLL_INSENSITIVE or other values. + * + *

+ * WARNING: Depending on the database type the underlying connection may never be freed -- even if you go all + * of the way through the results. It is strongly recommended that you call the + * {@link CloseableIterator#close()} method when you are done with the iterator. + *

+ */ + public CloseableIterator iterator(int resultFlags); + + /** + * Same as {@link #iterator()} but with a prepared query parameter. See {@link #queryBuilder} for more information. + * You use it like the following: + * + *
+	 * QueryBuilder<Account, String> qb = accountDao.queryBuilder();
+	 * ... custom query builder methods
+	 * CloseableIterator<Account> iterator = partialDao.iterator(qb.prepare());
+	 * try {
+	 *     while (iterator.hasNext()) {
+	 *         Account account = iterator.next();
+	 *         ...
+	 *     }
+	 * } finish {
+	 *     iterator.close();
+	 * }
+	 * 
+ * + * @param preparedQuery + * Query used to iterate across a sub-set of the items in the database. + * @return An iterator for T. + * @throws SQLException + * on any SQL problems. + */ + public CloseableIterator iterator(PreparedQuery preparedQuery) throws SQLException; + + /** + * Same as {@link #iterator(PreparedQuery)} but while specifying flags for the results. This is necessary with + * certain database types. + */ + public CloseableIterator iterator(PreparedQuery preparedQuery, int resultFlags) throws SQLException; + + /** + *

+ * This makes a one time use iterable class that can be closed afterwards. The DAO itself is + * {@link CloseableWrappedIterable} but multiple threads can each call this to get their own closeable iterable. + * This allows you to do something like: + *

+ * + *
+	 * CloseableWrappedIterable<Foo> wrappedIterable = fooDao.getWrappedIterable();
+	 * try {
+	 *   for (Foo foo : wrappedIterable) {
+	 *       ...
+	 *   }
+	 * } finally {
+	 *   wrappedIterable.close();
+	 * }
+	 * 
+ */ + public CloseableWrappedIterable getWrappedIterable(); + + /** + * Same as {@link #getWrappedIterable()} but with a prepared query parameter. See {@link #queryBuilder} or + * {@link #iterator(PreparedQuery)} for more information. + */ + public CloseableWrappedIterable getWrappedIterable(PreparedQuery preparedQuery); + + /** + * This closes the last iterator returned by the {@link #iterator()} method. + * + *

+ * NOTE: This is not reentrant. If multiple threads are getting iterators from this DAO then you should use + * the {@link #getWrappedIterable()} method to get a wrapped iterable for each thread instead. + *

+ */ + public void closeLastIterator() throws IOException; + + /** + *

+ * Similar to the {@link #iterator(PreparedQuery)} except it returns a GenericRawResults object associated with the + * SQL select query argument. Although you should use the {@link #iterator()} for most queries, this method allows + * you to do special queries that aren't supported otherwise. Like the above iterator methods, you must call close + * on the returned RawResults object once you are done with it. The arguments are optional but can be set with + * strings to expand ? type of SQL. + *

+ * + *

+ * You can use the {@link QueryBuilder#prepareStatementString()} method here if you want to build the query using + * the structure of the QueryBuilder. + *

+ * + *
+	 * QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();
+	 * qb.where().ge("orderCount", 10);
+	 * results = accountDao.queryRaw(qb.prepareStatementString());
+	 * 
+ * + *

+ * If you want to use the QueryBuilder with arguments to the raw query then you should do something like: + *

+ * + *
+	 * QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();
+	 * // we specify a SelectArg here to generate a ? in the statement string below
+	 * qb.where().ge("orderCount", new SelectArg());
+	 * // the 10 at the end is an optional argument to fulfill the SelectArg above
+	 * results = accountDao.queryRaw(qb.prepareStatementString(), rawRowMapper, 10);
+	 * 
+ * + *

+ * NOTE: If you are using the {@link QueryBuilder#prepareStatementString()} to build your query, it may have + * added the id column to the selected column list if the Dao object has an id you did not include it in the columns + * you selected. So the results might have one more column than you are expecting. + *

+ */ + public GenericRawResults queryRaw(String query, String... arguments) throws SQLException; + + /** + * Similar to the {@link #queryRaw(String, String...)} but this iterator returns rows that you can map yourself. For + * every result that is returned by the database, the {@link RawRowMapper#mapRow(String[], String[])} method is + * called so you can convert the result columns into an object to be returned by the iterator. The arguments are + * optional but can be set with strings to expand ? type of SQL. For a simple implementation of a raw row mapper, + * see {@link #getRawRowMapper()}. + */ + public GenericRawResults queryRaw(String query, RawRowMapper mapper, String... arguments) + throws SQLException; + + /** + * Similar to the {@link #queryRaw(String, RawRowMapper, String...)} but uses the column-types array to present an + * array of object results to the mapper instead of strings. The arguments are optional but can be set with strings + * to expand ? type of SQL. + */ + public GenericRawResults queryRaw(String query, DataType[] columnTypes, RawRowObjectMapper mapper, + String... arguments) throws SQLException; + + /** + * Similar to the {@link #queryRaw(String, String...)} but instead of an array of String results being returned by + * the iterator, this uses the column-types parameter to return an array of Objects instead. The arguments are + * optional but can be set with strings to expand ? type of SQL. + */ + public GenericRawResults queryRaw(String query, DataType[] columnTypes, String... arguments) + throws SQLException; + + /** + * Similar to the {@link #queryRaw(String, RawRowMapper, String...)} but this iterator returns rows that you can map + * yourself using {@link DatabaseResultsMapper}. + */ + public GenericRawResults queryRaw(String query, DatabaseResultsMapper mapper, String... arguments) + throws SQLException; + + /** + * Perform a raw query that returns a single value (usually an aggregate function like MAX or COUNT). If the query + * does not return a single long value then it will throw a SQLException. + */ + public long queryRawValue(String query, String... arguments) throws SQLException; + + /** + * Run a raw execute SQL statement to the database. The arguments are optional but can be set with strings to expand + * ? type of SQL. If you have no arguments, you may want to call {@link #executeRawNoArgs(String)}. + * + * @return number of rows affected. + */ + public int executeRaw(String statement, String... arguments) throws SQLException; + + /** + * Run a raw execute SQL statement on the database without any arguments. This may use a different mechanism to + * execute the query depending on the database backend. + * + * @return number of rows affected. + */ + public int executeRawNoArgs(String statement) throws SQLException; + + /** + * Run a raw update SQL statement to the database. The statement must be an SQL INSERT, UPDATE or DELETE + * statement.The arguments are optional but can be set with strings to expand ? type of SQL. + * + * @return number of rows affected. + */ + public int updateRaw(String statement, String... arguments) throws SQLException; + + /** + * Call the call-able that will perform a number of batch tasks. This is for performance when you want to run a + * number of database operations at once -- maybe loading data from a file. This will turn off what databases call + * "auto-commit" mode, run the call-able, and then re-enable "auto-commit". If auto-commit is not supported then a + * transaction will be used instead. + * + *

+ * NOTE: If neither auto-commit nor transactions are supported by the database type then this may just call + * the callable. Also, "commit()" is not called on the connection at all. If "auto-commit" is disabled then + * this will leave it off and nothing will have been persisted. + *

+ * + *

+ * NOTE: Depending on your underlying database implementation and whether or not you are working with a + * single database connection, this may synchronize internally to ensure that there are not race-conditions around + * the transactions on the single connection. Android (for example) will synchronize. Also, you may also need to + * synchronize calls to here and calls to {@link #setAutoCommit(DatabaseConnection, boolean)}. + *

+ */ + public CT callBatchTasks(Callable callable) throws Exception; + + /** + * Return the string version of the object with each of the known field values shown. Useful for testing and + * debugging. + * + * @param data + * The data item for which we are returning the toString information. + */ + public String objectToString(T data); + + /** + * Return true if the two parameters are equal. This checks each of the fields defined in the database to see if + * they are equal. Useful for testing and debugging. + * + * @param data1 + * One of the data items that we are checking for equality. + * @param data2 + * The other data item that we are checking for equality. + */ + public boolean objectsEqual(T data1, T data2) throws SQLException; + + /** + * Returns the ID from the data parameter passed in. This is used by some of the internal queries to be able to + * search by id. + */ + public ID extractId(T data) throws SQLException; + + /** + * Returns the class of the DAO. This is used by internal query operators. + */ + public Class getDataClass(); + + /** + * Returns the class of the DAO. This is used by internal query operators. + */ + public FieldType findForeignFieldType(Class clazz); + + /** + * Returns true if we can call update on this class. This is used most likely by folks who are extending the base + * dao classes. + */ + public boolean isUpdatable(); + + /** + * Returns true if the table already exists otherwise false. + */ + public boolean isTableExists() throws SQLException; + + /** + * Returns the number of rows in the table associated with the data class. Depending on the size of the table and + * the database type, this may be expensive and take a while. + */ + public long countOf() throws SQLException; + + /** + * Returns the number of rows in the table associated with the prepared query passed in. Depending on the size of + * the table and the database type, this may be expensive and take a while. + * + *

+ * NOTE: If the query was prepared with the {@link QueryBuilder} then you should have called the + * {@link QueryBuilder#setCountOf(boolean)} with true before you prepared the query. You may instead want to use + * {@link QueryBuilder#countOf()} which makes it all easier. + *

+ */ + public long countOf(PreparedQuery preparedQuery) throws SQLException; + + /** + *

+ * Creates an empty collection and assigns it to the appropriate field in the parent object. This allows you to add + * things to the collection from the start. + *

+ * + *

+ * For example let's say you have an Account which has the field: + *

+ * + *
+	 * @ForeignCollectionField(columnName = "orders")
+	 * Collection<Order> orders;
+	 * 
+ * + *

+ * You would then call: + *

+ * + *
+	 * accoundDao.assignEmptyForeignCollection(account, "orders");
+	 * // this would add it the collection and the internal DAO
+	 * account.orders.add(order1);
+	 * 
+ * + * @param parent + * Parent object that will be associated with all items added to this collection if not already assigned. + * @param fieldName + * parameter is the field name of the foreign collection field -- you might consider using the + * {@link ForeignCollectionField#columnName()} to set the name to a static name. + */ + public void assignEmptyForeignCollection(T parent, String fieldName) throws SQLException; + + /** + * Like {@link #assignEmptyForeignCollection(Object, String)} but it returns the empty collection that you assign to + * the appropriate field. + * + *

+ * NOTE: May be deprecated in the future. + *

+ */ + public ForeignCollection getEmptyForeignCollection(String fieldName) throws SQLException; + + /** + * Call this with true to enable an object cache for the DAO. Set to false to disable any caching. It is (as of + * 9/2011) one of the newer features of ORMLite. It keeps a {@link ReferenceObjectCache} of the objects (using + * {@link WeakReference}) referenced by the DAO. No support for objects returned by the {@link #queryRaw} methods. + * + * @throws SQLException + * If the DAO's class does not have an id field which is required by the {@link ObjectCache}. + */ + public void setObjectCache(boolean enabled) throws SQLException; + + /** + * Same as {@link #setObjectCache(boolean)} except you specify the actual cache instance to use for the DAO. This + * allows you to use a {@link ReferenceObjectCache} with {@link SoftReference} setting, the {@link LruObjectCache}, + * or inject your own cache implementation. Call it with null to disable the cache. + * + * @throws SQLException + * If the DAO's class does not have an id field which is required by the {@link ObjectCache}. + */ + public void setObjectCache(ObjectCache objectCache) throws SQLException; + + /** + * Returns the current object-cache being used by the DAO or null if none. + */ + public ObjectCache getObjectCache(); + + /** + * Flush the object cache if it has been enabled. This will remove an objects that are in the cache to reclaim + * memory. Any future queries will re-request them from the database. + */ + public void clearObjectCache(); + + /** + * Return the latest row from the database results from a query to select * (star). + */ + public T mapSelectStarRow(DatabaseResults results) throws SQLException; + + /** + * Return a row mapper that is suitable for mapping results from a query to select * (star). + */ + public GenericRowMapper getSelectStarRowMapper() throws SQLException; + + /** + * Return a row mapper that is suitable for use with {@link #queryRaw(String, RawRowMapper, String...)}. This is a + * bit experimental at this time. It most likely will _not_ work with all databases since the string output for each + * data type is hard to forecast. Please provide feedback. + */ + public RawRowMapper getRawRowMapper(); + + /** + * Returns true if an object exists that matches this ID otherwise false. + */ + public boolean idExists(ID id) throws SQLException; + + /** + *

+ * WARNING: This method is for advanced users only. It is only to support the + * {@link #setAutoCommit(DatabaseConnection, boolean)} and other methods below. Chances are you should be using the + * {@link #callBatchTasks(Callable)} instead of this method unless you know what you are doing. + *

+ * + *

+ * This allocates a connection for this specific thread that will be used in all other DAO operations. The thread + * must call {@link #endThreadConnection(DatabaseConnection)} once it is done with the connection. It is + * highly recommended that a + * try { conn = dao.startThreadConnection(); ... } finally { dao.endThreadConnection(conn); } type of + * pattern be used here to ensure you do not leak connections. + *

+ */ + public DatabaseConnection startThreadConnection() throws SQLException; + + /** + *

+ * WARNING: This method is for advanced users only. It is only to support the + * {@link #setAutoCommit(DatabaseConnection, boolean)} and other methods below. Chances are you should be using the + * {@link #callBatchTasks(Callable)} instead of this method unless you know what you are doing. + *

+ * + *

+ * This method is used to free the connection returned by the {@link #startThreadConnection()} above. + *

+ * + * @param connection + * Connection to be freed. If null then it will be a no-op. + */ + public void endThreadConnection(DatabaseConnection connection) throws SQLException; + + /** + * @deprecated You should use the {@link #setAutoCommit(DatabaseConnection, boolean)} method instead. + */ + @Deprecated + public void setAutoCommit(boolean autoCommit) throws SQLException; + + /** + * Set auto-commit mode to be true or false on the connection returned by the {@link #startThreadConnection()}. This + * may not be supported by all database types. + * + *

+ * WARNING: Chances are you should be using the {@link #callBatchTasks(Callable)} instead of this method + * unless you know what you are doing. + *

+ * + *

+ * NOTE: Depending on your underlying database implementation and whether or not you are working with a + * single database connection, you may need to synchronize calls to here and calls to + * {@link #callBatchTasks(Callable)}, {@link #commit(DatabaseConnection)}, and {@link #rollBack(DatabaseConnection)} + * . + *

+ */ + public void setAutoCommit(DatabaseConnection connection, boolean autoCommit) throws SQLException; + + /** + * @deprecated You should use the {@link #isAutoCommit(DatabaseConnection)} method instead. + */ + @Deprecated + public boolean isAutoCommit() throws SQLException; + + /** + * Return true if the database connection returned by the {@link #startThreadConnection()} is in auto-commit mode + * otherwise false. This may not be supported by all database types. + */ + public boolean isAutoCommit(DatabaseConnection connection) throws SQLException; + + /** + * If you have previously set auto-commit to false using {@link #setAutoCommit(DatabaseConnection, boolean)} then + * this will commit all changes to the database made from that point up to now on the connection returned by the + * {@link #startThreadConnection()}. The changes will be written to the database and discarded. The connection will + * continue to stay in the current auto-commit mode. + * + *

+ * WARNING: Chances are you should be using the {@link #callBatchTasks(Callable)} instead of this method + * unless you know what you are doing. + *

+ * + *

+ * NOTE: Depending on your underlying database implementation and whether or not you are working with a + * single database connection, you may need to synchronize calls to here and calls to + * {@link #callBatchTasks(Callable)}, {@link #setAutoCommit(DatabaseConnection, boolean)}, and + * {@link #rollBack(DatabaseConnection)}. + *

+ */ + public void commit(DatabaseConnection connection) throws SQLException; + + /** + * If you have previously set auto-commit to false using {@link #setAutoCommit(DatabaseConnection, boolean)} then + * this will roll-back and flush all changes to the database made from that point up to now on the connection + * returned by the {@link #startThreadConnection()} . None of those changes will be written to the database and are + * discarded. The connection will continue to stay in the current auto-commit mode. + * + *

+ * WARNING: Chances are you should be using the {@link #callBatchTasks(Callable)} instead of this method + * unless you know what you are doing. + *

+ * + *

+ * NOTE: Depending on your underlying database implementation and whether or not you are working with a + * single database connection, you may need to synchronize calls to here and calls to + * {@link #callBatchTasks(Callable)}, {@link #setAutoCommit(DatabaseConnection, boolean)}, and + * {@link #commit(DatabaseConnection)}. + *

+ */ + public void rollBack(DatabaseConnection connection) throws SQLException; + + /** + * Return the associated ConnectionSource or null if none set on the DAO yet. + */ + public ConnectionSource getConnectionSource(); + + /** + * Set an object factory so we can wire in controls over an object when it is constructed. Set to null to disable + * the factory. + */ + public void setObjectFactory(ObjectFactory objectFactory); + + /** + * Register an observer that will be called when data changes for this DAO. You mustq make a call to + * {@link #unregisterObserver(DaoObserver)} to de-register the observer after you are done with it. + */ + public void registerObserver(DaoObserver observer); + + /** + * Remove the observer from the registered list. + */ + public void unregisterObserver(DaoObserver observer); + + /** + * Notify any registered {@link DaoObserver}s that the underlying data may have changed. This is done automatically + * when using {@link #create(Object)}, {@link #update(Object)}, or {@link #delete(Object)} type methods. Batch + * methods will be notified once at the end of the batch, not for every statement in the batch. + * + * NOTE: The {@link #updateRaw(String, String...)} and other raw methods will _not_ call notify automatically. You + * will have to call this method yourself after you use the raw methods to change the entities. + */ + public void notifyChanges(); + + /** + * Return class for the {@link Dao#createOrUpdate(Object)} method. + */ + public static class CreateOrUpdateStatus { + private boolean created; + private boolean updated; + private int numLinesChanged; + public CreateOrUpdateStatus(boolean created, boolean updated, int numberLinesChanged) { + this.created = created; + this.updated = updated; + this.numLinesChanged = numberLinesChanged; + } + public boolean isCreated() { + return created; + } + public boolean isUpdated() { + return updated; + } + public int getNumLinesChanged() { + return numLinesChanged; + } + } + + /** + * Defines a class that can observe changes to entities managed by the DAO. + */ + public static interface DaoObserver { + /** + * Called when entities possibly have changed in the DAO. This can be used to detect changes to the entities + * managed by the DAO so that views can be updated. + */ + public void onChange(); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/DaoManager.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/DaoManager.java new file mode 100644 index 0000000..9d7348e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/DaoManager.java @@ -0,0 +1,455 @@ +package com.j256.ormlite.dao; + +import java.lang.reflect.Constructor; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.DatabaseTable; +import com.j256.ormlite.table.DatabaseTableConfig; + +/** + * Class which caches created DAOs. Sometimes internal DAOs are used to support such features as auto-refreshing of + * foreign fields or collections of sub-objects. Since instantiation of the DAO is a bit expensive, this class is used + * in an attempt to only create a DAO once for each class. + * + *

+ * NOTE: To use this cache, you should make sure you've added a {@link DatabaseTable#daoClass()} value to the + * annotation to the top of your class. + *

+ * + * @author graywatson + */ +public class DaoManager { + + private static Map, DatabaseTableConfig> configMap = null; + private static Map> classMap = null; + private static Map> tableConfigMap = null; + + private static Logger logger = LoggerFactory.getLogger(DaoManager.class); + + /** + * Helper method to create a DAO object without having to define a class. This checks to see if the DAO has already + * been created. If not then it is a call through to {@link BaseDaoImpl#createDao(ConnectionSource, Class)}. + */ + public synchronized static , T> D createDao(ConnectionSource connectionSource, Class clazz) + throws SQLException { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + ClassConnectionSource key = new ClassConnectionSource(connectionSource, clazz); + Dao dao = lookupDao(key); + if (dao != null) { + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + // see if we can build it from source + dao = createDaoFromConfig(connectionSource, clazz); + if (dao != null) { + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + DatabaseTable databaseTable = clazz.getAnnotation(DatabaseTable.class); + if (databaseTable == null || databaseTable.daoClass() == Void.class + || databaseTable.daoClass() == BaseDaoImpl.class) { + // see if the database type has some special table config extract method (Android) + DatabaseType databaseType = connectionSource.getDatabaseType(); + DatabaseTableConfig config = databaseType.extractDatabaseTableConfig(connectionSource, clazz); + Dao daoTmp; + if (config == null) { + daoTmp = BaseDaoImpl.createDao(connectionSource, clazz); + } else { + daoTmp = BaseDaoImpl.createDao(connectionSource, config); + } + dao = daoTmp; + logger.debug("created dao for class {} with reflection", clazz); + } else { + Class daoClass = databaseTable.daoClass(); + Object[] arguments = new Object[] { connectionSource, clazz }; + // look first for the constructor with a class parameter in case it is a generic dao + Constructor daoConstructor = findConstructor(daoClass, arguments); + if (daoConstructor == null) { + // then look for the constructor with just the ConnectionSource + arguments = new Object[] { connectionSource }; + daoConstructor = findConstructor(daoClass, arguments); + if (daoConstructor == null) { + throw new SQLException( + "Could not find public constructor with ConnectionSource and optional Class parameters " + + daoClass + ". Missing static on class?"); + } + } + try { + dao = (Dao) daoConstructor.newInstance(arguments); + logger.debug("created dao for class {} from constructor", clazz); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not call the constructor in class " + daoClass, e); + } + } + + registerDao(connectionSource, dao); + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + /** + * Helper method to lookup a DAO if it has already been associated with the class. Otherwise this returns null. + */ + public synchronized static , T> D lookupDao(ConnectionSource connectionSource, Class clazz) { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + ClassConnectionSource key = new ClassConnectionSource(connectionSource, clazz); + Dao dao = lookupDao(key); + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + /** + * Helper method to create a DAO object without having to define a class. This checks to see if the DAO has already + * been created. If not then it is a call through to + * {@link BaseDaoImpl#createDao(ConnectionSource, DatabaseTableConfig)}. + */ + public synchronized static , T> D createDao(ConnectionSource connectionSource, + DatabaseTableConfig tableConfig) throws SQLException { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + return doCreateDao(connectionSource, tableConfig); + } + + /** + * Helper method to lookup a DAO if it has already been associated with the table-config. Otherwise this returns + * null. + */ + public synchronized static , T> D lookupDao(ConnectionSource connectionSource, + DatabaseTableConfig tableConfig) { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + TableConfigConnectionSource key = new TableConfigConnectionSource(connectionSource, tableConfig); + Dao dao = lookupDao(key); + if (dao == null) { + return null; + } else { + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + } + + /** + * Register the DAO with the cache. This will allow folks to build a DAO externally and then register so it can be + * used internally as necessary. + * + *

+ * NOTE: By default this registers the DAO to be associated with the class that it uses. If you need to + * register multiple dao's that use different {@link DatabaseTableConfig}s then you should use + * {@link #registerDaoWithTableConfig(ConnectionSource, Dao)}. + *

+ * + *

+ * NOTE: You should maybe use the {@link DatabaseTable#daoClass()} and have the DaoManager construct the DAO + * if possible. + *

+ */ + public static synchronized void registerDao(ConnectionSource connectionSource, Dao dao) { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + addDaoToClassMap(new ClassConnectionSource(connectionSource, dao.getDataClass()), dao); + } + + /** + * Remove a DAO from the cache. This is necessary if we've registered it already but it throws an exception during + * configuration. + */ + public static synchronized void unregisterDao(ConnectionSource connectionSource, Dao dao) { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + removeDaoToClassMap(new ClassConnectionSource(connectionSource, dao.getDataClass()), dao); + } + + /** + * Same as {@link #registerDao(ConnectionSource, Dao)} but this allows you to register it just with its + * {@link DatabaseTableConfig}. This allows multiple versions of the DAO to be configured if necessary. + */ + public static synchronized void registerDaoWithTableConfig(ConnectionSource connectionSource, Dao dao) { + if (connectionSource == null) { + throw new IllegalArgumentException("connectionSource argument cannot be null"); + } + if (dao instanceof BaseDaoImpl) { + DatabaseTableConfig tableConfig = ((BaseDaoImpl) dao).getTableConfig(); + if (tableConfig != null) { + addDaoToTableMap(new TableConfigConnectionSource(connectionSource, tableConfig), dao); + return; + } + } + addDaoToClassMap(new ClassConnectionSource(connectionSource, dao.getDataClass()), dao); + } + + /** + * Clear out all of internal caches. + */ + public static synchronized void clearCache() { + if (configMap != null) { + configMap.clear(); + configMap = null; + } + clearDaoCache(); + } + + /** + * Clear out our DAO caches. + */ + public static synchronized void clearDaoCache() { + if (classMap != null) { + classMap.clear(); + classMap = null; + } + if (tableConfigMap != null) { + tableConfigMap.clear(); + tableConfigMap = null; + } + } + + /** + * This adds database table configurations to the internal cache which can be used to speed up DAO construction. + * This is especially true of Android and other mobile platforms. + */ + public static synchronized void addCachedDatabaseConfigs(Collection> configs) { + Map, DatabaseTableConfig> newMap; + if (configMap == null) { + newMap = new HashMap, DatabaseTableConfig>(); + } else { + newMap = new HashMap, DatabaseTableConfig>(configMap); + } + for (DatabaseTableConfig config : configs) { + newMap.put(config.getDataClass(), config); + logger.info("Loaded configuration for {}", config.getDataClass()); + } + configMap = newMap; + } + + private static void addDaoToClassMap(ClassConnectionSource key, Dao dao) { + if (classMap == null) { + classMap = new HashMap>(); + } + classMap.put(key, dao); + } + + private static void removeDaoToClassMap(ClassConnectionSource key, Dao dao) { + if (classMap != null) { + classMap.remove(key); + } + } + + private static void addDaoToTableMap(TableConfigConnectionSource key, Dao dao) { + if (tableConfigMap == null) { + tableConfigMap = new HashMap>(); + } + tableConfigMap.put(key, dao); + } + + private static Dao lookupDao(ClassConnectionSource key) { + if (classMap == null) { + classMap = new HashMap>(); + } + Dao dao = classMap.get(key); + if (dao == null) { + return null; + } else { + return dao; + } + } + + private static Dao lookupDao(TableConfigConnectionSource key) { + if (tableConfigMap == null) { + tableConfigMap = new HashMap>(); + } + Dao dao = tableConfigMap.get(key); + if (dao == null) { + return null; + } else { + return dao; + } + } + + private static Constructor findConstructor(Class daoClass, Object[] params) { + for (Constructor constructor : daoClass.getConstructors()) { + Class[] paramsTypes = constructor.getParameterTypes(); + if (paramsTypes.length == params.length) { + boolean match = true; + for (int i = 0; i < paramsTypes.length; i++) { + if (!paramsTypes[i].isAssignableFrom(params[i].getClass())) { + match = false; + break; + } + } + if (match) { + return constructor; + } + } + } + return null; + } + + /** + * Creates the DAO if we have config information cached and caches the DAO. + */ + private static D createDaoFromConfig(ConnectionSource connectionSource, Class clazz) throws SQLException { + // no loaded configs + if (configMap == null) { + return null; + } + + @SuppressWarnings("unchecked") + DatabaseTableConfig config = (DatabaseTableConfig) configMap.get(clazz); + // if we don't config information cached return null + if (config == null) { + return null; + } + + // else create a DAO using configuration + Dao configedDao = doCreateDao(connectionSource, config); + @SuppressWarnings("unchecked") + D castDao = (D) configedDao; + return castDao; + } + + private static , T> D doCreateDao(ConnectionSource connectionSource, + DatabaseTableConfig tableConfig) throws SQLException { + TableConfigConnectionSource tableKey = new TableConfigConnectionSource(connectionSource, tableConfig); + // look up in the table map + Dao dao = lookupDao(tableKey); + if (dao != null) { + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + // now look it up in the class map + Class dataClass = tableConfig.getDataClass(); + ClassConnectionSource classKey = new ClassConnectionSource(connectionSource, dataClass); + dao = lookupDao(classKey); + if (dao != null) { + // if it is not in the table map but is in the class map, add it + addDaoToTableMap(tableKey, dao); + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + // build the DAO using the table information + DatabaseTable databaseTable = tableConfig.getDataClass().getAnnotation(DatabaseTable.class); + if (databaseTable == null || databaseTable.daoClass() == Void.class + || databaseTable.daoClass() == BaseDaoImpl.class) { + Dao daoTmp = BaseDaoImpl.createDao(connectionSource, tableConfig); + dao = daoTmp; + } else { + Class daoClass = databaseTable.daoClass(); + Object[] arguments = new Object[] { connectionSource, tableConfig }; + Constructor constructor = findConstructor(daoClass, arguments); + if (constructor == null) { + throw new SQLException( + "Could not find public constructor with ConnectionSource, DatabaseTableConfig parameters in class " + + daoClass); + } + try { + dao = (Dao) constructor.newInstance(arguments); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not call the constructor in class " + daoClass, e); + } + } + + addDaoToTableMap(tableKey, dao); + logger.debug("created dao for class {} from table config", dataClass); + + // if it is not in the class config either then add it + if (lookupDao(classKey) == null) { + addDaoToClassMap(classKey, dao); + } + + @SuppressWarnings("unchecked") + D castDao = (D) dao; + return castDao; + } + + /** + * Key for our class DAO map. + */ + private static class ClassConnectionSource { + ConnectionSource connectionSource; + Class clazz; + public ClassConnectionSource(ConnectionSource connectionSource, Class clazz) { + this.connectionSource = connectionSource; + this.clazz = clazz; + } + @Override + public int hashCode() { + final int prime = 31; + int result = prime + clazz.hashCode(); + result = prime * result + connectionSource.hashCode(); + return result; + } + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ClassConnectionSource other = (ClassConnectionSource) obj; + if (!clazz.equals(other.clazz)) { + return false; + } else if (!connectionSource.equals(other.connectionSource)) { + return false; + } else { + return true; + } + } + } + + /** + * Key for our table-config DAO map. + */ + private static class TableConfigConnectionSource { + ConnectionSource connectionSource; + DatabaseTableConfig tableConfig; + public TableConfigConnectionSource(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) { + this.connectionSource = connectionSource; + this.tableConfig = tableConfig; + } + @Override + public int hashCode() { + final int prime = 31; + int result = prime + tableConfig.hashCode(); + result = prime * result + connectionSource.hashCode(); + return result; + } + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TableConfigConnectionSource other = (TableConfigConnectionSource) obj; + if (!tableConfig.equals(other.tableConfig)) { + return false; + } else if (!connectionSource.equals(other.connectionSource)) { + return false; + } else { + return true; + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/DatabaseResultsMapper.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/DatabaseResultsMapper.java new file mode 100644 index 0000000..610f225 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/DatabaseResultsMapper.java @@ -0,0 +1,28 @@ +package com.j256.ormlite.dao; + +import java.sql.SQLException; + +import com.j256.ormlite.support.DatabaseResults; + +/** + * Result apper that utilized the raw {@link DatabaseResults} object. See + * {@link Dao#queryRaw(String, DatabaseResultsMapper, String...)}. + * + * @author nonameplum + */ +public interface DatabaseResultsMapper { + + /** + * Map the row with the raw DatabaseResults object. This method should not cause the result set to advance or + * otherwise change position. If you are using under JDBC then you can cast the DatabaseResults object to be + * {@code JdbcDatabaseResults} and get the raw ResultSet using {@code jdbcDatabaseResults.getResultSet()}. + * + * @param databaseResults + * The results entry that is currently being iterated. You must not advance or call any of the other move + * operations on this parameter. + * @return The value to return for this row to be included in the raw results iterator. + * @throws SQLException + * If there is any critical error with the data and you want to stop the paging. + */ + public T mapRow(DatabaseResults databaseResults) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/EagerForeignCollection.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/EagerForeignCollection.java new file mode 100644 index 0000000..f19e432 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/EagerForeignCollection.java @@ -0,0 +1,291 @@ +package com.j256.ormlite.dao; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Collection that is set on a field that as been marked with the {@link ForeignCollectionField} annotation when an + * object is refreshed or queried (i.e. not created). + * + * @author graywatson + */ +public class EagerForeignCollection extends BaseForeignCollection implements CloseableWrappedIterable, + Serializable { + + private static final long serialVersionUID = -2523335606983317721L; + + private List results; + + /** + * WARNING: The user should not be calling this constructor. You should be using the + * {@link Dao#assignEmptyForeignCollection(Object, String)} or {@link Dao#getEmptyForeignCollection(String)} methods + * instead. + */ + public EagerForeignCollection(Dao dao, Object parent, Object parentId, FieldType foreignFieldType, + String orderColumn, boolean orderAscending) throws SQLException { + super(dao, parent, parentId, foreignFieldType, orderColumn, orderAscending); + if (parentId == null) { + /* + * If we have no field value then just create an empty list. This is for when we need to create an empty + * eager collection. + */ + results = new ArrayList(); + } else { + // go ahead and do the query if eager + results = dao.query(getPreparedQuery()); + } + } + + public CloseableIterator iterator() { + return iteratorThrow(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iterator(int flags) { + return iteratorThrow(flags); + } + + public CloseableIterator closeableIterator() { + return iteratorThrow(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator closeableIterator(int flags) { + return iteratorThrow(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iteratorThrow() { + return iteratorThrow(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iteratorThrow(int flags) { + // we have to wrap the iterator since we are returning the List's iterator + return new CloseableIterator() { + private int offset = -1; + public boolean hasNext() { + return (offset + 1 < results.size()); + } + public T first() { + offset = 0; + if (offset >= results.size()) { + return null; + } else { + return results.get(0); + } + } + public T next() { + offset++; + // this should throw if OOB + return results.get(offset); + } + public T nextThrow() { + offset++; + if (offset >= results.size()) { + return null; + } else { + return results.get(offset); + } + } + public T current() { + if (offset < 0) { + offset = 0; + } + if (offset >= results.size()) { + return null; + } else { + return results.get(offset); + } + } + public T previous() { + offset--; + if (offset < 0 || offset >= results.size()) { + return null; + } else { + return results.get(offset); + } + } + public T moveRelative(int relativeOffset) { + offset += relativeOffset; + if (offset < 0 || offset >= results.size()) { + return null; + } else { + return results.get(offset); + } + } + public void remove() { + if (offset < 0) { + throw new IllegalStateException("next() must be called before remove()"); + } + if (offset >= results.size()) { + throw new IllegalStateException("current results position (" + offset + ") is out of bounds"); + } + T removed = results.remove(offset); + offset--; + if (dao != null) { + try { + dao.delete(removed); + } catch (SQLException e) { + // have to demote this to be runtime + throw new RuntimeException(e); + } + } + } + public void close() { + // noop + } + public void closeQuietly() { + // noop + } + public DatabaseResults getRawResults() { + // no results object + return null; + } + public void moveToNext() { + offset++; + } + }; + } + + public CloseableWrappedIterable getWrappedIterable() { + // since the iterators don't have any connections, the collection can be a wrapped iterable + return this; + } + + public CloseableWrappedIterable getWrappedIterable(int flags) { + return this; + } + + public void close() { + // noop since the iterators aren't holding open a connection + } + + public void closeLastIterator() { + // noop since the iterators aren't holding open a connection + } + + public boolean isEager() { + return true; + } + + public int size() { + return results.size(); + } + + public boolean isEmpty() { + return results.isEmpty(); + } + + public boolean contains(Object o) { + return results.contains(o); + } + + public boolean containsAll(Collection c) { + return results.containsAll(c); + } + + public Object[] toArray() { + return results.toArray(); + } + + public E[] toArray(E[] array) { + return results.toArray(array); + } + + @Override + public boolean add(T data) { + if (results.add(data)) { + return super.add(data); + } else { + return false; + } + } + + @Override + public boolean addAll(Collection collection) { + if (results.addAll(collection)) { + return super.addAll(collection); + } else { + return false; + } + } + + @Override + public boolean remove(Object data) { + if (!results.remove(data) || dao == null) { + return false; + } + + @SuppressWarnings("unchecked") + T castData = (T) data; + try { + return (dao.delete(castData) == 1); + } catch (SQLException e) { + throw new IllegalStateException("Could not delete data element from dao", e); + } + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + for (Object data : collection) { + if (remove(data)) { + changed = true; + } + } + return changed; + } + + @Override + public boolean retainAll(Collection collection) { + // delete from the iterate removes from the eager list and dao + return super.retainAll(collection); + } + + public int updateAll() throws SQLException { + int updatedC = 0; + for (T data : results) { + updatedC += dao.update(data); + } + return updatedC; + } + + public int refreshAll() throws SQLException { + int updatedC = 0; + for (T data : results) { + updatedC += dao.refresh(data); + } + return updatedC; + } + + public int refreshCollection() throws SQLException { + results = dao.query(getPreparedQuery()); + return results.size(); + } + + /** + * This is just a call to the equals method of the internal results list. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof EagerForeignCollection)) { + return false; + } + @SuppressWarnings("rawtypes") + EagerForeignCollection other = (EagerForeignCollection) obj; + return results.equals(other.results); + } + + /** + * This is just a call to the hashcode method of the internal results list. + */ + @Override + public int hashCode() { + return results.hashCode(); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/ForeignCollection.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/ForeignCollection.java new file mode 100644 index 0000000..173ee4f --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/ForeignCollection.java @@ -0,0 +1,139 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collection; + +import com.j256.ormlite.field.ForeignCollectionField; + +/** + *

+ * Collection that is set on a field that as been marked with the {@link ForeignCollectionField} annotation when an + * object is refreshed or queried (i.e. not created). + *

+ * + *
+ * @ForeignCollectionField(eager = false)
+ * private ForeignCollection<Order> orders;
+ * 
+ * + *

+ * NOTE: If the collection has been marked as being "lazy" then just about all methods in this class result in a + * pass through the database using the {@link #iterator()}. Even {@link #size()} and other seemingly simple calls can + * cause a lot of database I/O. Most likely just the {@link #iterator()}, {@link #toArray()}, and + * {@link #toArray(Object[])} methods should be used if you are using a lazy collection. Any other methods have no + * guarantee to be at all efficient. Take a look at the source if you have any question. + *

+ * + *

+ * NOTE: It is also important to remember that lazy iterators hold a connection open to the database which needs + * to be closed. See {@link LazyForeignCollection#iterator()}. + *

+ * + * @author graywatson + */ +public interface ForeignCollection extends Collection, CloseableIterable { + + /** + * Like {@link Collection#iterator()} but while specifying flags for the results. This is necessary with certain + * database types. The resultFlags could be something like ResultSet.TYPE_SCROLL_INSENSITIVE or other values. + */ + public CloseableIterator iterator(int flags); + + /** + * Same as {@link #iterator(int)}. + */ + public CloseableIterator closeableIterator(int flags); + + /** + * Like {@link Collection#iterator()} but returns a closeable iterator instead and can throw a SQLException. + */ + public CloseableIterator iteratorThrow() throws SQLException; + + /** + * Like {@link #iteratorThrow()} but while specifying flags for the results. This is necessary with certain database + * types. The resultFlags could be something like ResultSet.TYPE_SCROLL_INSENSITIVE or other values. + */ + public CloseableIterator iteratorThrow(int flags) throws SQLException; + + /** + * This makes a one time use iterable class that can be closed afterwards. The ForeignCollection itself is + * {@link CloseableWrappedIterable} but multiple threads can each call this to get their own closeable iterable. + */ + public CloseableWrappedIterable getWrappedIterable(); + + /** + * Like {@link #getWrappedIterable()} but while specifying flags for the results. This is necessary with certain + * database types. The resultFlags could be something like ResultSet.TYPE_SCROLL_INSENSITIVE or other values. + */ + public CloseableWrappedIterable getWrappedIterable(int flags); + + /** + * This will close the last iterator returned by the {@link #iterator()} method. + * + *

+ * NOTE: For lazy collections, this is not reentrant. If multiple threads are getting iterators from a lazy + * collection from the same object then you should use {@link #getWrappedIterable()} to get a reentrant wrapped + * iterable for each thread instead. + *

+ */ + public void closeLastIterator() throws IOException; + + /** + * Returns true if this an eager collection otherwise false. + */ + public boolean isEager(); + + /** + * This is a call through to {@link Dao#update(Object)} using the internal collection DAO. Objects inside of the + * collection are not updated if the parent object is refreshed so you will need to so that by hand. + */ + public int update(T obj) throws SQLException; + + /** + * Update all of the items currently in the collection with the database. This is only applicable for eager + * collections. + * + * @return The number of rows updated. + */ + public int updateAll() throws SQLException; + + /** + * This is a call through to {@link Dao#refresh(Object)} using the internal collection DAO. Objects inside of the + * collection are not refreshed if the parent object is refreshed so you will need to so that by hand. + */ + public int refresh(T obj) throws SQLException; + + /** + * Call to refresh on all of the items currently in the collection with the database. This is only applicable + * for eager collections. If you want to see new objects in the collection then you should use + * {@link #refreshCollection()}. + * + * @return The number of rows refreshed. + */ + public int refreshAll() throws SQLException; + + /** + * This re-issues the query that initially built the collection replacing any underlying result collection with a + * new one build from the database. This is only applicable for eager collections and is a no-op for lazy + * collections. + * + * @return The number of objects loaded into the new collection. + */ + public int refreshCollection() throws SQLException; + + /** + * Adds the object to the collection. This will also add it to the database by calling through to [@link + * {@link Dao#create(Object)}. If the object has already been created in the database then you just need to set the + * foreign field on the object and call {@link Dao#update(Object)}. If you add it here the DAO will try to create it + * in the database again which will most likely cause an error. + * + * @see Collection#add(Object) + */ + public boolean add(T obj); + + /** + * Return the DAO object associated with this foreign collection. For usage for those who know what they are doing. + */ + public Dao getDao(); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/GenericRawResults.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/GenericRawResults.java new file mode 100644 index 0000000..0dc6714 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/GenericRawResults.java @@ -0,0 +1,60 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.field.DataType; + +/** + * Results returned by a call to {@link Dao#queryRaw(String, String...)} which returns results as a String[], + * {@link Dao#queryRaw(String, RawRowMapper, String...)} which returns results mapped by the caller to an Object, and + * {@link Dao#queryRaw(String, DataType[], String...)} which returns each results as a Object[]. + * + *

+ * You must access the results one of three ways using this object. You can call the {@link #getResults()} method which + * will extract all results into a list which is returned, you can get the first result only with + * {@link #getFirstResult()}, or you can call the {@link #iterator()} method either directly or with the for... Java + * statement. The iterator allows you to page through the results and is more appropriate for queries which will return + * a large number of results. + *

+ * + *

+ * NOTE: If you access the {@link #iterator()} method, you must call {@link CloseableIterator#close()} method + * when you are done otherwise the underlying SQL statement and connection may be kept open. + *

+ * + * @author graywatson + */ +public interface GenericRawResults extends CloseableWrappedIterable { + + /** + * Return the number of columns in each result row. + */ + public int getNumberColumns(); + + /** + * Return the array of column names for each result row. + */ + public String[] getColumnNames(); + + /** + * Return a list of all of the results. For large queries, this should not be used since the {@link #iterator()} + * method will allow your to process the results page-by-page. + */ + public List getResults() throws SQLException; + + /** + * Return the first result only. This should be used if you are only expecting one result. It cannot be used in + * conjunction with {@link #getResults()}. + * + * @return null if there are no results. + */ + public T getFirstResult() throws SQLException; + + /** + * Close any open database connections associated with the GenericRawResults. This is only necessary if the + * {@link Dao#iterator()} or another iterator method was called. + */ + public void close() throws IOException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/LazyForeignCollection.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/LazyForeignCollection.java new file mode 100644 index 0000000..276565c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/LazyForeignCollection.java @@ -0,0 +1,291 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; +import java.io.Serializable; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.support.DatabaseConnection; + +/** + * Collection that is set on a field that as been marked with the {@link ForeignCollectionField} annotation when an + * object is refreshed or queried (i.e. not created). Most of the methods here require a pass through the database. + * Operations such as size() therefore should most likely not be used because of their expense. Chances are you only + * want to use the {@link #iterator()}, {@link #toArray()}, and {@link #toArray(Object[])} methods. + * + *

+ * WARNING: Most likely for(;;) loops should not be used here since we need to be careful about closing the + * iterator. + *

+ * + * @author graywatson + */ +public class LazyForeignCollection extends BaseForeignCollection implements Serializable { + + private static final long serialVersionUID = -5460708106909626233L; + + private transient CloseableIterator lastIterator; + + /** + * WARNING: The user should not be calling this constructor. You should be using the + * {@link Dao#assignEmptyForeignCollection(Object, String)} or {@link Dao#getEmptyForeignCollection(String)} methods + * instead. + */ + public LazyForeignCollection(Dao dao, Object parent, Object parentId, FieldType foreignFieldType, + String orderColumn, boolean orderAscending) { + super(dao, parent, parentId, foreignFieldType, orderColumn, orderAscending); + } + + /** + * The iterator returned from a lazy collection keeps a connection open to the database as it iterates across the + * collection. You will need to call {@link CloseableIterator#close()} or go all the way through the loop to ensure + * that the connection has been closed. You can also call {@link #closeLastIterator()} on the collection itself + * which will close the last iterator returned. See the reentrant warning. + */ + public CloseableIterator iterator() { + return closeableIterator(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iterator(int flags) { + return closeableIterator(flags); + } + + public CloseableIterator closeableIterator() { + return closeableIterator(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator closeableIterator(int flags) { + try { + return iteratorThrow(flags); + } catch (SQLException e) { + throw new IllegalStateException("Could not build lazy iterator for " + dao.getDataClass(), e); + } + } + + public CloseableIterator iteratorThrow() throws SQLException { + return iteratorThrow(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableIterator iteratorThrow(int flags) throws SQLException { + lastIterator = seperateIteratorThrow(flags); + return lastIterator; + } + + public CloseableWrappedIterable getWrappedIterable() { + return getWrappedIterable(DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CloseableWrappedIterable getWrappedIterable(final int flags) { + return new CloseableWrappedIterableImpl(new CloseableIterable() { + public CloseableIterator iterator() { + return closeableIterator(); + } + public CloseableIterator closeableIterator() { + try { + return LazyForeignCollection.this.seperateIteratorThrow(flags); + } catch (Exception e) { + throw new IllegalStateException("Could not build lazy iterator for " + dao.getDataClass(), e); + } + } + }); + } + + public void closeLastIterator() throws IOException { + if (lastIterator != null) { + lastIterator.close(); + lastIterator = null; + } + } + + public boolean isEager() { + return false; + } + + public int size() { + CloseableIterator iterator = iterator(); + try { + int sizeC; + for (sizeC = 0; iterator.hasNext(); sizeC++) { + // move to next without constructing the object + iterator.moveToNext(); + } + return sizeC; + } finally { + IOUtils.closeQuietly(iterator); + } + } + + public boolean isEmpty() { + CloseableIterator iterator = iterator(); + try { + return !iterator.hasNext(); + } finally { + IOUtils.closeQuietly(iterator); + } + } + + public boolean contains(Object obj) { + CloseableIterator iterator = iterator(); + try { + while (iterator.hasNext()) { + if (iterator.next().equals(obj)) { + return true; + } + } + return false; + } finally { + IOUtils.closeQuietly(iterator); + } + } + + public boolean containsAll(Collection collection) { + Set leftOvers = new HashSet(collection); + CloseableIterator iterator = iterator(); + try { + while (iterator.hasNext()) { + leftOvers.remove(iterator.next()); + } + return leftOvers.isEmpty(); + } finally { + IOUtils.closeQuietly(iterator); + } + } + + @Override + public boolean remove(Object data) { + CloseableIterator iterator = iterator(); + try { + while (iterator.hasNext()) { + if (iterator.next().equals(data)) { + iterator.remove(); + return true; + } + } + return false; + } finally { + IOUtils.closeQuietly(iterator); + } + } + + @Override + public boolean removeAll(Collection collection) { + boolean changed = false; + CloseableIterator iterator = iterator(); + try { + while (iterator.hasNext()) { + if (collection.contains(iterator.next())) { + iterator.remove(); + changed = true; + } + } + return changed; + } finally { + IOUtils.closeQuietly(iterator); + } + } + + public Object[] toArray() { + List items = new ArrayList(); + CloseableIterator iterator = iterator(); + try { + while (iterator.hasNext()) { + items.add(iterator.next()); + } + return items.toArray(); + } finally { + IOUtils.closeQuietly(iterator); + } + } + + public E[] toArray(E[] array) { + List items = null; + int itemC = 0; + CloseableIterator iterator = iterator(); + try { + while (iterator.hasNext()) { + @SuppressWarnings("unchecked") + E castData = (E) iterator.next(); + // are we exceeding our capacity in the array? + if (itemC >= array.length) { + if (items == null) { + items = new ArrayList(); + for (E arrayData : array) { + items.add(arrayData); + } + } + items.add(castData); + } else { + array[itemC] = castData; + } + itemC++; + } + } finally { + IOUtils.closeQuietly(iterator); + } + if (items == null) { + if (itemC < array.length - 1) { + array[itemC] = null; + } + return array; + } else { + return items.toArray(array); + } + } + + public int updateAll() { + throw new UnsupportedOperationException("Cannot call updateAll() on a lazy collection."); + } + + public int refreshAll() { + throw new UnsupportedOperationException("Cannot call updateAll() on a lazy collection."); + } + + public int refreshCollection() { + // no-op for lazy collections + return 0; + } + + /** + * This is just a call to {@link Object#equals(Object)}. + * + *

+ * NOTE: This method is here for documentation purposes because {@link EagerForeignCollection#equals(Object)} is + * defined. + *

+ */ + @Override + public boolean equals(Object other) { + return super.equals(other); + } + + /** + * This is just a call to {@link Object#hashCode()}. + * + *

+ * NOTE: This method is here for documentation purposes because {@link EagerForeignCollection#equals(Object)} is + * defined. + *

+ */ + @Override + public int hashCode() { + return super.hashCode(); + } + + private CloseableIterator seperateIteratorThrow(int flags) throws SQLException { + // check state to make sure we have a DAO in case we have a deserialized collection + if (dao == null) { + throw new IllegalStateException( + "Internal DAO object is null. Maybe the collection was deserialized or otherwise constructed wrongly. " + + "Use dao.assignEmptyForeignCollection(...) or dao.getEmptyForeignCollection(...) instead"); + } else { + return dao.iterator(getPreparedQuery(), flags); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/LruObjectCache.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/LruObjectCache.java new file mode 100644 index 0000000..ff62ce5 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/LruObjectCache.java @@ -0,0 +1,136 @@ +package com.j256.ormlite.dao; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Cache for ORMLite which stores a certain number of items for each Class. Inserting an object into the cache once it + * is full will cause the least-recently-used object to be ejected. They can be injected into a dao with the + * {@link Dao#setObjectCache(ObjectCache)}. + * + *

+ * NOTE: If you set the capacity to be 100 then each Class will allow 100 items in the cache. If you have + * 5 classes then the cache will hold 500 objects. + *

+ * + * @author graywatson + */ +public class LruObjectCache implements ObjectCache { + + private final int capacity; + private final ConcurrentHashMap, Map> classMaps = + new ConcurrentHashMap, Map>(); + + public LruObjectCache(int capacity) { + this.capacity = capacity; + } + + public synchronized void registerClass(Class clazz) { + Map objectMap = classMaps.get(clazz); + if (objectMap == null) { + objectMap = Collections.synchronizedMap(new LimitedLinkedHashMap(capacity)); + classMaps.put(clazz, objectMap); + } + } + + public T get(Class clazz, ID id) { + Map objectMap = getMapForClass(clazz); + if (objectMap == null) { + return null; + } + Object obj = objectMap.get(id); + @SuppressWarnings("unchecked") + T castObj = (T) obj; + return castObj; + } + + public void put(Class clazz, ID id, T data) { + Map objectMap = getMapForClass(clazz); + if (objectMap != null) { + objectMap.put(id, data); + } + } + + public void clear(Class clazz) { + Map objectMap = getMapForClass(clazz); + if (objectMap != null) { + objectMap.clear(); + } + } + + public void clearAll() { + for (Map objectMap : classMaps.values()) { + objectMap.clear(); + } + } + + public void remove(Class clazz, ID id) { + Map objectMap = getMapForClass(clazz); + if (objectMap != null) { + objectMap.remove(id); + } + } + + public T updateId(Class clazz, ID oldId, ID newId) { + Map objectMap = getMapForClass(clazz); + if (objectMap == null) { + return null; + } + Object obj = objectMap.remove(oldId); + if (obj == null) { + return null; + } + objectMap.put(newId, obj); + @SuppressWarnings("unchecked") + T castObj = (T) obj; + return castObj; + } + + public int size(Class clazz) { + Map objectMap = getMapForClass(clazz); + if (objectMap == null) { + return 0; + } else { + return objectMap.size(); + } + } + + public int sizeAll() { + int size = 0; + for (Map objectMap : classMaps.values()) { + size += objectMap.size(); + } + return size; + } + + private Map getMapForClass(Class clazz) { + Map objectMap = classMaps.get(clazz); + if (objectMap == null) { + return null; + } else { + return objectMap; + } + } + + /** + * Little extension of the LimitedLinkedHashMap + */ + private static class LimitedLinkedHashMap extends LinkedHashMap { + + private static final long serialVersionUID = -4566528080395573236L; + private final int capacity; + + public LimitedLinkedHashMap(int capacity) { + super(capacity, 0.75F, true); + this.capacity = capacity; + } + + @Override + protected boolean removeEldestEntry(Entry eldest) { + return size() > capacity; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/ObjectCache.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/ObjectCache.java new file mode 100644 index 0000000..879a85d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/ObjectCache.java @@ -0,0 +1,62 @@ +package com.j256.ormlite.dao; + +/** + * Definition of an object cache that can be injected into the Dao with the {@link Dao#setObjectCache(ObjectCache)}. + * + *

+ * NOTE: Most of the below methods take a Class argument but your cache can be for a single cache. If this is the + * case then you should protect against storing different classes in the cache. + *

+ * + * @author graywatson + */ +public interface ObjectCache { + + /** + * Register a class for use with this class. This will be called before any other method for the particular class is + * called. + */ + public void registerClass(Class clazz); + + /** + * Lookup in the cache for an object of a certain class that has a certain id. + * + * @return The found object or null if none. + */ + public T get(Class clazz, ID id); + + /** + * Put an object in the cache that has a certain class and id. + */ + public void put(Class clazz, ID id, T data); + + /** + * Delete from the cache an object of a certain class that has a certain id. + */ + public void remove(Class clazz, ID id); + + /** + * Change the id in the cache for an object of a certain class from an old-id to a new-id. + */ + public T updateId(Class clazz, ID oldId, ID newId); + + /** + * Remove all entries from the cache of a certain class. + */ + public void clear(Class clazz); + + /** + * Remove all entries from the cache of all classes. + */ + public void clearAll(); + + /** + * Return the number of elements in the cache. + */ + public int size(Class clazz); + + /** + * Return the number of elements in all of the caches. + */ + public int sizeAll(); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowMapper.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowMapper.java new file mode 100644 index 0000000..47992b1 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowMapper.java @@ -0,0 +1,42 @@ +package com.j256.ormlite.dao; + +import java.sql.SQLException; + +import com.j256.ormlite.stmt.QueryBuilder; + +/** + * Parameterized row mapper that takes output from the {@link GenericRawResults} and returns a T. Is used in the + * {@link Dao#queryRaw(String, RawRowMapper, String...)} method. + * + *

+ * NOTE: If you need to map Objects instead then consider using the {@link RawRowObjectMapper} with the + * {@link Dao#queryRaw(String, com.j256.ormlite.field.DataType[], RawRowObjectMapper, String...)} method which allows + * you to iterate over the raw results as Object[]. + *

+ * + * @param + * Type that the mapRow returns. + * @author graywatson + */ +public interface RawRowMapper { + + /** + * Used to convert a raw results row to an object. + * + *

+ * NOTE: If you are using the {@link QueryBuilder#prepareStatementString()} to build your query, it may have + * added the id column to the selected column list if the Dao object has an id you did not include it in the columns + * you selected. So the results might have one more column than you are expecting. + *

+ * + * @return The created object with all of the fields set from the results. Return null if there is no object + * generated from these results. + * @param columnNames + * Array of names of columns. + * @param resultColumns + * Array of result columns. + * @throws SQLException + * If there is any critical error with the data and you want to stop the paging. + */ + public T mapRow(String[] columnNames, String[] resultColumns) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowObjectMapper.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowObjectMapper.java new file mode 100644 index 0000000..08c7520 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/RawRowObjectMapper.java @@ -0,0 +1,46 @@ +package com.j256.ormlite.dao; + +import java.sql.SQLException; + +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.stmt.QueryBuilder; + +/** + * Parameterized row mapper that takes output from the {@link GenericRawResults} and returns a T. Is used in the + * {@link Dao#queryRaw(String, DataType[], RawRowObjectMapper, String...)} method. + * + *

+ * NOTE: If you need to map Strings instead then consider using the {@link RawRowMapper} with the + * {@link Dao#queryRaw(String, RawRowMapper, String...)} method which allows you to iterate over the raw results as + * String[]. + *

+ * + * @param + * Type that the mapRow returns. + * @author graywatson + */ +public interface RawRowObjectMapper { + + /** + * Used to convert a raw results row to an object. + * + *

+ * NOTE: If you are using the {@link QueryBuilder#prepareStatementString()} to build your query, it may have + * added the id column to the selected column list if the Dao object has an id you did not include it in the columns + * you selected. So the results might have one more column than you are expecting. + *

+ * + * @return The created object with all of the fields set from the results. Return null if there is no object + * generated from these results. + * @param columnNames + * Array of names of columns. + * @param dataTypes + * Array of the DataTypes of each of the columns as passed into the + * {@link Dao#queryRaw(String, DataType[], RawRowObjectMapper, String...)} + * @param resultColumns + * Array of result columns. + * @throws SQLException + * If there is any critical error with the data and you want to stop the paging. + */ + public T mapRow(String[] columnNames, DataType[] dataTypes, Object[] resultColumns) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/ReferenceObjectCache.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/ReferenceObjectCache.java new file mode 100644 index 0000000..7e3c248 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/ReferenceObjectCache.java @@ -0,0 +1,174 @@ +package com.j256.ormlite.dao; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Cache for ORMLite which stores objects with a {@link WeakReference} or {@link SoftReference} to them. Java Garbage + * Collection can then free these objects if no one has a "strong" reference to the object (weak) or if it runs out of + * memory (soft). + * + * @author graywatson + */ +public class ReferenceObjectCache implements ObjectCache { + + private final ConcurrentHashMap, Map>> classMaps = + new ConcurrentHashMap, Map>>(); + private final boolean useWeak; + + /** + * @param useWeak + * Set to true if you want the cache to use {@link WeakReference}. If false then the cache will use + * {@link SoftReference}. + */ + public ReferenceObjectCache(boolean useWeak) { + this.useWeak = useWeak; + } + + /** + * Create and return an object cache using {@link WeakReference}. + */ + public static ReferenceObjectCache makeWeakCache() { + return new ReferenceObjectCache(true); + } + + /** + * Create and return an object cache using {@link SoftReference}. + */ + public static ReferenceObjectCache makeSoftCache() { + return new ReferenceObjectCache(false); + } + + public synchronized void registerClass(Class clazz) { + Map> objectMap = classMaps.get(clazz); + if (objectMap == null) { + objectMap = new ConcurrentHashMap>(); + classMaps.put(clazz, objectMap); + } + } + + public T get(Class clazz, ID id) { + Map> objectMap = getMapForClass(clazz); + if (objectMap == null) { + return null; + } + Reference ref = objectMap.get(id); + if (ref == null) { + return null; + } + Object obj = ref.get(); + if (obj == null) { + objectMap.remove(id); + return null; + } else { + @SuppressWarnings("unchecked") + T castObj = (T) obj; + return castObj; + } + } + + public void put(Class clazz, ID id, T data) { + Map> objectMap = getMapForClass(clazz); + if (objectMap != null) { + if (useWeak) { + objectMap.put(id, new WeakReference(data)); + } else { + objectMap.put(id, new SoftReference(data)); + } + } + } + + public void clear(Class clazz) { + Map> objectMap = getMapForClass(clazz); + if (objectMap != null) { + objectMap.clear(); + } + } + + public void clearAll() { + for (Map> objectMap : classMaps.values()) { + objectMap.clear(); + } + } + + public void remove(Class clazz, ID id) { + Map> objectMap = getMapForClass(clazz); + if (objectMap != null) { + objectMap.remove(id); + } + } + + public T updateId(Class clazz, ID oldId, ID newId) { + Map> objectMap = getMapForClass(clazz); + if (objectMap == null) { + return null; + } + Reference ref = objectMap.remove(oldId); + if (ref == null) { + return null; + } + objectMap.put(newId, ref); + @SuppressWarnings("unchecked") + T castObj = (T) ref.get(); + return castObj; + } + + public int size(Class clazz) { + Map> objectMap = getMapForClass(clazz); + if (objectMap == null) { + return 0; + } else { + return objectMap.size(); + } + } + + public int sizeAll() { + int size = 0; + for (Map> objectMap : classMaps.values()) { + size += objectMap.size(); + } + return size; + } + + /** + * Run through the map and remove any references that have been null'd out by the GC. + */ + public void cleanNullReferences(Class clazz) { + Map> objectMap = getMapForClass(clazz); + if (objectMap != null) { + cleanMap(objectMap); + } + } + + /** + * Run through all maps and remove any references that have been null'd out by the GC. + */ + public void cleanNullReferencesAll() { + for (Map> objectMap : classMaps.values()) { + cleanMap(objectMap); + } + } + + private void cleanMap(Map> objectMap) { + Iterator>> iterator = objectMap.entrySet().iterator(); + while (iterator.hasNext()) { + if (iterator.next().getValue().get() == null) { + iterator.remove(); + } + } + } + + private Map> getMapForClass(Class clazz) { + Map> objectMap = classMaps.get(clazz); + if (objectMap == null) { + return null; + } else { + return objectMap; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/dao/RuntimeExceptionDao.java b/MemeProject/app/src/main/java/com/j256/ormlite/dao/RuntimeExceptionDao.java new file mode 100644 index 0000000..da709c5 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/dao/RuntimeExceptionDao.java @@ -0,0 +1,883 @@ +package com.j256.ormlite.dao; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import com.j256.ormlite.dao.Dao.CreateOrUpdateStatus; +import com.j256.ormlite.dao.Dao.DaoObserver; +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.logger.Log.Level; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.stmt.DeleteBuilder; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.stmt.PreparedDelete; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.stmt.PreparedUpdate; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.UpdateBuilder; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.DatabaseTableConfig; +import com.j256.ormlite.table.ObjectFactory; + +/** + * Proxy to a {@link Dao} that wraps each Exception and rethrows it as RuntimeException. You can use this if your usage + * pattern is to ignore all exceptions. That's not a pattern that I like so it's not the default. + * + *
+ * RuntimeExceptionDao<Account, String> accountDao = RuntimeExceptionDao.createDao(connectionSource, Account.class);
+ * 
+ * + * @author graywatson + */ +public class RuntimeExceptionDao implements CloseableIterable { + + /* + * We use debug here because we don't want these messages to be logged by default. The user will need to turn on + * logging for this class to DEBUG to see the messages. + */ + private static final Level LOG_LEVEL = Level.DEBUG; + + private Dao dao; + private static final Logger logger = LoggerFactory.getLogger(RuntimeExceptionDao.class); + + public RuntimeExceptionDao(Dao dao) { + this.dao = dao; + } + + /** + * Call through to {@link DaoManager#createDao(ConnectionSource, Class)} with the returned DAO wrapped in a + * RuntimeExceptionDao. + */ + public static RuntimeExceptionDao createDao(ConnectionSource connectionSource, Class clazz) + throws SQLException { + @SuppressWarnings("unchecked") + Dao castDao = (Dao) DaoManager.createDao(connectionSource, clazz); + return new RuntimeExceptionDao(castDao); + } + + /** + * Call through to {@link DaoManager#createDao(ConnectionSource, DatabaseTableConfig)} with the returned DAO wrapped + * in a RuntimeExceptionDao. + */ + public static RuntimeExceptionDao createDao(ConnectionSource connectionSource, + DatabaseTableConfig tableConfig) throws SQLException { + @SuppressWarnings("unchecked") + Dao castDao = (Dao) DaoManager.createDao(connectionSource, tableConfig); + return new RuntimeExceptionDao(castDao); + } + + /** + * @see Dao#queryForId(Object) + */ + public T queryForId(ID id) { + try { + return dao.queryForId(id); + } catch (SQLException e) { + logMessage(e, "queryForId threw exception on: " + id); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForFirst(PreparedQuery) + */ + public T queryForFirst(PreparedQuery preparedQuery) { + try { + return dao.queryForFirst(preparedQuery); + } catch (SQLException e) { + logMessage(e, "queryForFirst threw exception on: " + preparedQuery); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForAll() + */ + public List queryForAll() { + try { + return dao.queryForAll(); + } catch (SQLException e) { + logMessage(e, "queryForAll threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForEq(String, Object) + */ + public List queryForEq(String fieldName, Object value) { + try { + return dao.queryForEq(fieldName, value); + } catch (SQLException e) { + logMessage(e, "queryForEq threw exception on: " + fieldName); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForMatching(Object) + */ + public List queryForMatching(T matchObj) { + try { + return dao.queryForMatching(matchObj); + } catch (SQLException e) { + logMessage(e, "queryForMatching threw exception on: " + matchObj); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForMatchingArgs(Object) + */ + public List queryForMatchingArgs(T matchObj) { + try { + return dao.queryForMatchingArgs(matchObj); + } catch (SQLException e) { + logMessage(e, "queryForMatchingArgs threw exception on: " + matchObj); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForFieldValues(Map) + */ + public List queryForFieldValues(Map fieldValues) { + try { + return dao.queryForFieldValues(fieldValues); + } catch (SQLException e) { + logMessage(e, "queryForFieldValues threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForFieldValuesArgs(Map) + */ + public List queryForFieldValuesArgs(Map fieldValues) { + try { + return dao.queryForFieldValuesArgs(fieldValues); + } catch (SQLException e) { + logMessage(e, "queryForFieldValuesArgs threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryForSameId(Object) + */ + public T queryForSameId(T data) { + try { + return dao.queryForSameId(data); + } catch (SQLException e) { + logMessage(e, "queryForSameId threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryBuilder() + */ + public QueryBuilder queryBuilder() { + return dao.queryBuilder(); + } + + /** + * @see Dao#updateBuilder() + */ + public UpdateBuilder updateBuilder() { + return dao.updateBuilder(); + } + + /** + * @see Dao#deleteBuilder() + */ + public DeleteBuilder deleteBuilder() { + return dao.deleteBuilder(); + } + + /** + * @see Dao#query(PreparedQuery) + */ + public List query(PreparedQuery preparedQuery) { + try { + return dao.query(preparedQuery); + } catch (SQLException e) { + logMessage(e, "query threw exception on: " + preparedQuery); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#create(Object) + */ + public int create(T data) { + try { + return dao.create(data); + } catch (SQLException e) { + logMessage(e, "create threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#create(Collection) + */ + public int create(Collection datas) { + try { + return dao.create(datas); + } catch (SQLException e) { + logMessage(e, "create threw exception on: " + datas); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#createIfNotExists(Object) + */ + public T createIfNotExists(T data) { + try { + return dao.createIfNotExists(data); + } catch (SQLException e) { + logMessage(e, "createIfNotExists threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#createOrUpdate(Object) + */ + public CreateOrUpdateStatus createOrUpdate(T data) { + try { + return dao.createOrUpdate(data); + } catch (SQLException e) { + logMessage(e, "createOrUpdate threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#update(Object) + */ + public int update(T data) { + try { + return dao.update(data); + } catch (SQLException e) { + logMessage(e, "update threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#updateId(Object, Object) + */ + public int updateId(T data, ID newId) { + try { + return dao.updateId(data, newId); + } catch (SQLException e) { + logMessage(e, "updateId threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#update(PreparedUpdate) + */ + public int update(PreparedUpdate preparedUpdate) { + try { + return dao.update(preparedUpdate); + } catch (SQLException e) { + logMessage(e, "update threw exception on: " + preparedUpdate); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#refresh(Object) + */ + public int refresh(T data) { + try { + return dao.refresh(data); + } catch (SQLException e) { + logMessage(e, "refresh threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#delete(Object) + */ + public int delete(T data) { + try { + return dao.delete(data); + } catch (SQLException e) { + logMessage(e, "delete threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#deleteById(Object) + */ + public int deleteById(ID id) { + try { + return dao.deleteById(id); + } catch (SQLException e) { + logMessage(e, "deleteById threw exception on: " + id); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#delete(Collection) + */ + public int delete(Collection datas) { + try { + return dao.delete(datas); + } catch (SQLException e) { + logMessage(e, "delete threw exception on: " + datas); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#deleteIds(Collection) + */ + public int deleteIds(Collection ids) { + try { + return dao.deleteIds(ids); + } catch (SQLException e) { + logMessage(e, "deleteIds threw exception on: " + ids); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#delete(PreparedDelete) + */ + public int delete(PreparedDelete preparedDelete) { + try { + return dao.delete(preparedDelete); + } catch (SQLException e) { + logMessage(e, "delete threw exception on: " + preparedDelete); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#iterator() + */ + public CloseableIterator iterator() { + return dao.iterator(); + } + + public CloseableIterator closeableIterator() { + return dao.closeableIterator(); + } + + /** + * @see Dao#iterator(int) + */ + public CloseableIterator iterator(int resultFlags) { + return dao.iterator(resultFlags); + } + + /** + * @see Dao#getWrappedIterable() + */ + public CloseableWrappedIterable getWrappedIterable() { + return dao.getWrappedIterable(); + } + + /** + * @see Dao#getWrappedIterable(PreparedQuery) + */ + public CloseableWrappedIterable getWrappedIterable(PreparedQuery preparedQuery) { + return dao.getWrappedIterable(preparedQuery); + } + + /** + * @see Dao#closeLastIterator() + */ + public void closeLastIterator() { + try { + dao.closeLastIterator(); + } catch (IOException e) { + logMessage(e, "closeLastIterator threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#iterator(PreparedQuery) + */ + public CloseableIterator iterator(PreparedQuery preparedQuery) { + try { + return dao.iterator(preparedQuery); + } catch (SQLException e) { + logMessage(e, "iterator threw exception on: " + preparedQuery); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#iterator(PreparedQuery, int) + */ + public CloseableIterator iterator(PreparedQuery preparedQuery, int resultFlags) { + try { + return dao.iterator(preparedQuery, resultFlags); + } catch (SQLException e) { + logMessage(e, "iterator threw exception on: " + preparedQuery); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryRaw(String, String...) + */ + public GenericRawResults queryRaw(String query, String... arguments) { + try { + return dao.queryRaw(query, arguments); + } catch (SQLException e) { + logMessage(e, "queryRaw threw exception on: " + query); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryRawValue(String, String...) + */ + public long queryRawValue(String query, String... arguments) { + try { + return dao.queryRawValue(query, arguments); + } catch (SQLException e) { + logMessage(e, "queryRawValue threw exception on: " + query); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryRaw(String, RawRowMapper, String...) + */ + public GenericRawResults queryRaw(String query, RawRowMapper mapper, String... arguments) { + try { + return dao.queryRaw(query, mapper, arguments); + } catch (SQLException e) { + logMessage(e, "queryRaw threw exception on: " + query); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryRaw(String, DataType[], RawRowObjectMapper, String...) + */ + public GenericRawResults queryRaw(String query, DataType[] columnTypes, RawRowObjectMapper mapper, + String... arguments) { + try { + return dao.queryRaw(query, columnTypes, mapper, arguments); + } catch (SQLException e) { + logMessage(e, "queryRaw threw exception on: " + query); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryRaw(String, DataType[], String...) + */ + public GenericRawResults queryRaw(String query, DataType[] columnTypes, String... arguments) { + try { + return dao.queryRaw(query, columnTypes, arguments); + } catch (SQLException e) { + logMessage(e, "queryRaw threw exception on: " + query); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#queryRaw(String, DatabaseResultsMapper, String...) + */ + public GenericRawResults queryRaw(String query, DatabaseResultsMapper mapper, String... arguments) { + try { + return dao.queryRaw(query, mapper, arguments); + } catch (SQLException e) { + logMessage(e, "queryRaw threw exception on: " + query); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#executeRaw(String, String...) + */ + public int executeRaw(String statement, String... arguments) { + try { + return dao.executeRaw(statement, arguments); + } catch (SQLException e) { + logMessage(e, "executeRaw threw exception on: " + statement); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#executeRawNoArgs(String) + */ + public int executeRawNoArgs(String statement) { + try { + return dao.executeRawNoArgs(statement); + } catch (SQLException e) { + logMessage(e, "executeRawNoArgs threw exception on: " + statement); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#updateRaw(String, String...) + */ + public int updateRaw(String statement, String... arguments) { + try { + return dao.updateRaw(statement, arguments); + } catch (SQLException e) { + logMessage(e, "updateRaw threw exception on: " + statement); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#callBatchTasks(Callable) + */ + public CT callBatchTasks(Callable callable) { + try { + return dao.callBatchTasks(callable); + } catch (Exception e) { + logMessage(e, "callBatchTasks threw exception on: " + callable); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#objectToString(Object) + */ + public String objectToString(T data) { + return dao.objectToString(data); + } + + /** + * @see Dao#objectsEqual(Object, Object) + */ + public boolean objectsEqual(T data1, T data2) { + try { + return dao.objectsEqual(data1, data2); + } catch (SQLException e) { + logMessage(e, "objectsEqual threw exception on: " + data1 + " and " + data2); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#extractId(Object) + */ + public ID extractId(T data) { + try { + return dao.extractId(data); + } catch (SQLException e) { + logMessage(e, "extractId threw exception on: " + data); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#getDataClass() + */ + public Class getDataClass() { + return dao.getDataClass(); + } + + /** + * @see Dao#findForeignFieldType(Class) + */ + public FieldType findForeignFieldType(Class clazz) { + return dao.findForeignFieldType(clazz); + } + + /** + * @see Dao#isUpdatable() + */ + public boolean isUpdatable() { + return dao.isUpdatable(); + } + + /** + * @see Dao#isTableExists() + */ + public boolean isTableExists() { + try { + return dao.isTableExists(); + } catch (SQLException e) { + logMessage(e, "isTableExists threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#countOf() + */ + public long countOf() { + try { + return dao.countOf(); + } catch (SQLException e) { + logMessage(e, "countOf threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#countOf(PreparedQuery) + */ + public long countOf(PreparedQuery preparedQuery) { + try { + return dao.countOf(preparedQuery); + } catch (SQLException e) { + logMessage(e, "countOf threw exception on " + preparedQuery); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#assignEmptyForeignCollection(Object, String) + */ + public void assignEmptyForeignCollection(T parent, String fieldName) { + try { + dao.assignEmptyForeignCollection(parent, fieldName); + } catch (SQLException e) { + logMessage(e, "assignEmptyForeignCollection threw exception on " + fieldName); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#getEmptyForeignCollection(String) + */ + public ForeignCollection getEmptyForeignCollection(String fieldName) { + try { + return dao.getEmptyForeignCollection(fieldName); + } catch (SQLException e) { + logMessage(e, "getEmptyForeignCollection threw exception on " + fieldName); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#setObjectCache(boolean) + */ + public void setObjectCache(boolean enabled) { + try { + dao.setObjectCache(enabled); + } catch (SQLException e) { + logMessage(e, "setObjectCache(" + enabled + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#getObjectCache() + */ + public ObjectCache getObjectCache() { + return dao.getObjectCache(); + } + + /** + * @see Dao#setObjectCache(ObjectCache) + */ + public void setObjectCache(ObjectCache objectCache) { + try { + dao.setObjectCache(objectCache); + } catch (SQLException e) { + logMessage(e, "setObjectCache threw exception on " + objectCache); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#clearObjectCache() + */ + public void clearObjectCache() { + dao.clearObjectCache(); + } + + /** + * @see Dao#mapSelectStarRow(DatabaseResults) + */ + public T mapSelectStarRow(DatabaseResults results) { + try { + return dao.mapSelectStarRow(results); + } catch (SQLException e) { + logMessage(e, "mapSelectStarRow threw exception on results"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#getSelectStarRowMapper() + */ + public GenericRowMapper getSelectStarRowMapper() { + try { + return dao.getSelectStarRowMapper(); + } catch (SQLException e) { + logMessage(e, "getSelectStarRowMapper threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#idExists(Object) + */ + public boolean idExists(ID id) { + try { + return dao.idExists(id); + } catch (SQLException e) { + logMessage(e, "idExists threw exception on " + id); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#startThreadConnection() + */ + public DatabaseConnection startThreadConnection() { + try { + return dao.startThreadConnection(); + } catch (SQLException e) { + logMessage(e, "startThreadConnection() threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#endThreadConnection(DatabaseConnection) + */ + public void endThreadConnection(DatabaseConnection connection) { + try { + dao.endThreadConnection(connection); + } catch (SQLException e) { + logMessage(e, "endThreadConnection(" + connection + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#setAutoCommit(boolean) + */ + @Deprecated + public void setAutoCommit(boolean autoCommit) { + try { + dao.setAutoCommit(autoCommit); + } catch (SQLException e) { + logMessage(e, "setAutoCommit(" + autoCommit + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#setAutoCommit(DatabaseConnection, boolean) + */ + public void setAutoCommit(DatabaseConnection connection, boolean autoCommit) { + try { + dao.setAutoCommit(connection, autoCommit); + } catch (SQLException e) { + logMessage(e, "setAutoCommit(" + connection + "," + autoCommit + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#isAutoCommit() + */ + @Deprecated + public boolean isAutoCommit() { + try { + return dao.isAutoCommit(); + } catch (SQLException e) { + logMessage(e, "isAutoCommit() threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#isAutoCommit(DatabaseConnection) + */ + public boolean isAutoCommit(DatabaseConnection connection) { + try { + return dao.isAutoCommit(connection); + } catch (SQLException e) { + logMessage(e, "isAutoCommit(" + connection + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#commit(DatabaseConnection) + */ + public void commit(DatabaseConnection connection) { + try { + dao.commit(connection); + } catch (SQLException e) { + logMessage(e, "commit(" + connection + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#rollBack(DatabaseConnection) + */ + public void rollBack(DatabaseConnection connection) { + try { + dao.rollBack(connection); + } catch (SQLException e) { + logMessage(e, "rollBack(" + connection + ") threw exception"); + throw new RuntimeException(e); + } + } + + /** + * @see Dao#setObjectFactory(ObjectFactory) + */ + public void setObjectFactory(ObjectFactory objectFactory) { + dao.setObjectFactory(objectFactory); + } + + /** + * @see Dao#getRawRowMapper() + */ + public RawRowMapper getRawRowMapper() { + return dao.getRawRowMapper(); + } + + /** + * @see Dao#getConnectionSource() + */ + public ConnectionSource getConnectionSource() { + return dao.getConnectionSource(); + } + + public void registerObserver(DaoObserver observer) { + dao.registerObserver(observer); + } + + public void unregisterObserver(DaoObserver observer) { + dao.unregisterObserver(observer); + } + + public void notifyChanges() { + dao.notifyChanges(); + } + + private void logMessage(Exception e, String message) { + logger.log(LOG_LEVEL, e, message); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/db/BaseDatabaseType.java b/MemeProject/app/src/main/java/com/j256/ormlite/db/BaseDatabaseType.java new file mode 100644 index 0000000..df241cf --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/db/BaseDatabaseType.java @@ -0,0 +1,555 @@ +package com.j256.ormlite.db; + +import java.sql.Driver; +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.field.BaseFieldConverter; +import com.j256.ormlite.field.DataPersister; +import com.j256.ormlite.field.FieldConverter; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.DatabaseTableConfig; + +/** + * Base class for all of the {@link DatabaseType} classes that provide the per-database type functionality to create + * tables and build queries. + * + *

+ * Here's a good page which shows some of the differences between SQL + * databases. + *

+ * + * @author graywatson + */ +public abstract class BaseDatabaseType implements DatabaseType { + + protected static String DEFAULT_SEQUENCE_SUFFIX = "_id_seq"; + protected Driver driver; + + /** + * Return the name of the driver class associated with this database type. + */ + protected abstract String getDriverClassName(); + + public void loadDriver() throws SQLException { + String className = getDriverClassName(); + if (className != null) { + // this instantiates the driver class which wires in the JDBC glue + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + throw SqlExceptionUtil.create("Driver class was not found for " + getDatabaseName() + + " database. Missing jar with class " + className + ".", e); + } + } + } + + public void setDriver(Driver driver) { + this.driver = driver; + } + + public void appendColumnArg(String tableName, StringBuilder sb, FieldType fieldType, List additionalArgs, + List statementsBefore, List statementsAfter, List queriesAfter) throws SQLException { + appendEscapedEntityName(sb, fieldType.getColumnName()); + sb.append(' '); + DataPersister dataPersister = fieldType.getDataPersister(); + // first try the per-field width + int fieldWidth = fieldType.getWidth(); + if (fieldWidth == 0) { + // next try the per-data-type width + fieldWidth = dataPersister.getDefaultWidth(); + } + switch (dataPersister.getSqlType()) { + + case STRING : + appendStringType(sb, fieldType, fieldWidth); + break; + + case LONG_STRING : + appendLongStringType(sb, fieldType, fieldWidth); + break; + + case BOOLEAN : + appendBooleanType(sb, fieldType, fieldWidth); + break; + + case DATE : + appendDateType(sb, fieldType, fieldWidth); + break; + + case CHAR : + appendCharType(sb, fieldType, fieldWidth); + break; + + case BYTE : + appendByteType(sb, fieldType, fieldWidth); + break; + + case BYTE_ARRAY : + appendByteArrayType(sb, fieldType, fieldWidth); + break; + + case SHORT : + appendShortType(sb, fieldType, fieldWidth); + break; + + case INTEGER : + appendIntegerType(sb, fieldType, fieldWidth); + break; + + case LONG : + appendLongType(sb, fieldType, fieldWidth); + break; + + case FLOAT : + appendFloatType(sb, fieldType, fieldWidth); + break; + + case DOUBLE : + appendDoubleType(sb, fieldType, fieldWidth); + break; + + case SERIALIZABLE : + appendSerializableType(sb, fieldType, fieldWidth); + break; + + case BIG_DECIMAL : + appendBigDecimalNumericType(sb, fieldType, fieldWidth); + break; + + case UUID : + appendUuidNativeType(sb, fieldType, fieldWidth); + break; + + case UNKNOWN : + default : + // shouldn't be able to get here unless we have a missing case + throw new IllegalArgumentException("Unknown SQL-type " + dataPersister.getSqlType()); + } + sb.append(' '); + + /* + * NOTE: the configure id methods must be in this order since isGeneratedIdSequence is also isGeneratedId and + * isId. isGeneratedId is also isId. + */ + if (fieldType.isGeneratedIdSequence() && !fieldType.isSelfGeneratedId()) { + configureGeneratedIdSequence(sb, fieldType, statementsBefore, additionalArgs, queriesAfter); + } else if (fieldType.isGeneratedId() && !fieldType.isSelfGeneratedId()) { + configureGeneratedId(tableName, sb, fieldType, statementsBefore, statementsAfter, additionalArgs, + queriesAfter); + } else if (fieldType.isId()) { + configureId(sb, fieldType, statementsBefore, additionalArgs, queriesAfter); + } + // if we have a generated-id then neither the not-null nor the default make sense and cause syntax errors + if (!fieldType.isGeneratedId()) { + Object defaultValue = fieldType.getDefaultValue(); + if (defaultValue != null) { + sb.append("DEFAULT "); + appendDefaultValue(sb, fieldType, defaultValue); + sb.append(' '); + } + if (fieldType.isCanBeNull()) { + appendCanBeNull(sb, fieldType); + } else { + sb.append("NOT NULL "); + } + if (fieldType.isUnique()) { + addSingleUnique(sb, fieldType, additionalArgs, statementsAfter); + } + } + } + + /** + * Output the SQL type for a Java String. + */ + protected void appendStringType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + if (isVarcharFieldWidthSupported()) { + sb.append("VARCHAR(").append(fieldWidth).append(")"); + } else { + sb.append("VARCHAR"); + } + } + + /** + * Output the SQL type for a Java UUID. This is used to support specific sub-class database types which support the + * UUID type. + */ + protected void appendUuidNativeType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + throw new UnsupportedOperationException("UUID is not supported by this database type"); + } + + /** + * Output the SQL type for a Java Long String. + */ + protected void appendLongStringType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("TEXT"); + } + + /** + * Output the SQL type for a Java Date. + */ + protected void appendDateType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("TIMESTAMP"); + } + + /** + * Output the SQL type for a Java boolean. + */ + protected void appendBooleanType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("BOOLEAN"); + } + + /** + * Output the SQL type for a Java char. + */ + protected void appendCharType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("CHAR"); + } + + /** + * Output the SQL type for a Java byte. + */ + protected void appendByteType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("TINYINT"); + } + + /** + * Output the SQL type for a Java short. + */ + protected void appendShortType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("SMALLINT"); + } + + /** + * Output the SQL type for a Java integer. + */ + private void appendIntegerType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("INTEGER"); + } + + /** + * Output the SQL type for a Java long. + */ + protected void appendLongType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("BIGINT"); + } + + /** + * Output the SQL type for a Java float. + */ + private void appendFloatType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("FLOAT"); + } + + /** + * Output the SQL type for a Java double. + */ + private void appendDoubleType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("DOUBLE PRECISION"); + } + + /** + * Output the SQL type for either a serialized Java object or a byte[]. + */ + protected void appendByteArrayType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("BLOB"); + } + + /** + * Output the SQL type for a serialized Java object. + */ + protected void appendSerializableType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("BLOB"); + } + + /** + * Output the SQL type for a BigDecimal object. + */ + protected void appendBigDecimalNumericType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + sb.append("NUMERIC"); + } + + /** + * Output the SQL type for the default value for the type. + */ + private void appendDefaultValue(StringBuilder sb, FieldType fieldType, Object defaultValue) { + if (fieldType.isEscapedDefaultValue()) { + appendEscapedWord(sb, defaultValue.toString()); + } else { + sb.append(defaultValue); + } + } + + /** + * Output the SQL necessary to configure a generated-id column. This may add to the before statements list or + * additional arguments later. + * + * NOTE: Only one of configureGeneratedIdSequence, configureGeneratedId, or configureId will be called. + */ + protected void configureGeneratedIdSequence(StringBuilder sb, FieldType fieldType, List statementsBefore, + List additionalArgs, List queriesAfter) throws SQLException { + throw new SQLException("GeneratedIdSequence is not supported by database " + getDatabaseName() + " for field " + + fieldType); + } + + /** + * Output the SQL necessary to configure a generated-id column. This may add to the before statements list or + * additional arguments later. + * + * NOTE: Only one of configureGeneratedIdSequence, configureGeneratedId, or configureId will be called. + */ + protected void configureGeneratedId(String tableName, StringBuilder sb, FieldType fieldType, + List statementsBefore, List statementsAfter, List additionalArgs, + List queriesAfter) { + throw new IllegalStateException("GeneratedId is not supported by database " + getDatabaseName() + " for field " + + fieldType); + } + + /** + * Output the SQL necessary to configure an id column. This may add to the before statements list or additional + * arguments later. + * + * NOTE: Only one of configureGeneratedIdSequence, configureGeneratedId, or configureId will be called. + */ + protected void configureId(StringBuilder sb, FieldType fieldType, List statementsBefore, + List additionalArgs, List queriesAfter) { + // default is noop since we do it at the end in appendPrimaryKeys() + } + + public void addPrimaryKeySql(FieldType[] fieldTypes, List additionalArgs, List statementsBefore, + List statementsAfter, List queriesAfter) { + StringBuilder sb = null; + for (FieldType fieldType : fieldTypes) { + if (fieldType.isGeneratedId() && !generatedIdSqlAtEnd() && !fieldType.isSelfGeneratedId()) { + // don't add anything + } else if (fieldType.isId()) { + if (sb == null) { + sb = new StringBuilder(48); + sb.append("PRIMARY KEY ("); + } else { + sb.append(','); + } + appendEscapedEntityName(sb, fieldType.getColumnName()); + } + } + if (sb != null) { + sb.append(") "); + additionalArgs.add(sb.toString()); + } + } + + /** + * Return true if we should add generated-id SQL in the {@link #addPrimaryKeySql} method at the end. If false then + * it needs to be done by hand inline. + */ + protected boolean generatedIdSqlAtEnd() { + return true; + } + + public void addUniqueComboSql(FieldType[] fieldTypes, List additionalArgs, List statementsBefore, + List statementsAfter, List queriesAfter) { + StringBuilder sb = null; + for (FieldType fieldType : fieldTypes) { + if (fieldType.isUniqueCombo()) { + if (sb == null) { + sb = new StringBuilder(48); + sb.append("UNIQUE ("); + } else { + sb.append(','); + } + appendEscapedEntityName(sb, fieldType.getColumnName()); + } + } + if (sb != null) { + sb.append(") "); + additionalArgs.add(sb.toString()); + } + } + + public void dropColumnArg(FieldType fieldType, List statementsBefore, List statementsAfter) { + // by default this is a noop + } + + public void appendEscapedWord(StringBuilder sb, String word) { + sb.append('\'').append(word).append('\''); + } + + public void appendEscapedEntityName(StringBuilder sb, String name) { + sb.append('`').append(name).append('`'); + } + + public String generateIdSequenceName(String tableName, FieldType idFieldType) { + String name = tableName + DEFAULT_SEQUENCE_SUFFIX; + if (isEntityNamesMustBeUpCase()) { + return name.toUpperCase(); + } else { + return name; + } + } + + public String getCommentLinePrefix() { + return "-- "; + } + + public DataPersister getDataPersister(DataPersister defaultPersister, FieldType fieldType) { + // default is noop + return defaultPersister; + } + + public FieldConverter getFieldConverter(DataPersister dataPersister, FieldType fieldType) { + // default is to use the dataPersister itself + return dataPersister; + } + + public boolean isIdSequenceNeeded() { + return false; + } + + public boolean isVarcharFieldWidthSupported() { + return true; + } + + public boolean isLimitSqlSupported() { + return true; + } + + public boolean isOffsetSqlSupported() { + return true; + } + + public boolean isOffsetLimitArgument() { + return false; + } + + public boolean isLimitAfterSelect() { + return false; + } + + public void appendLimitValue(StringBuilder sb, long limit, Long offset) { + sb.append("LIMIT ").append(limit).append(' '); + } + + public void appendOffsetValue(StringBuilder sb, long offset) { + sb.append("OFFSET ").append(offset).append(' '); + } + + public void appendSelectNextValFromSequence(StringBuilder sb, String sequenceName) { + // noop by default. + } + + public void appendCreateTableSuffix(StringBuilder sb) { + // noop by default. + } + + public boolean isCreateTableReturnsZero() { + return true; + } + + public boolean isCreateTableReturnsNegative() { + return false; + } + + public boolean isEntityNamesMustBeUpCase() { + return false; + } + + public boolean isNestedSavePointsSupported() { + return true; + } + + public String getPingStatement() { + return "SELECT 1"; + } + + public boolean isBatchUseTransaction() { + return false; + } + + public boolean isTruncateSupported() { + return false; + } + + public boolean isCreateIfNotExistsSupported() { + return false; + } + + public boolean isCreateIndexIfNotExistsSupported() { + return isCreateIfNotExistsSupported(); + } + + public boolean isSelectSequenceBeforeInsert() { + return false; + } + + public boolean isAllowGeneratedIdInsertSupported() { + return true; + } + + /** + * @throws SQLException + * for sub classes. + */ + public DatabaseTableConfig extractDatabaseTableConfig(ConnectionSource connectionSource, Class clazz) + throws SQLException { + // default is no default extractor + return null; + } + + public void appendInsertNoColumns(StringBuilder sb) { + sb.append("() VALUES ()"); + } + + /** + * If the field can be nullable, do we need to add some sort of NULL SQL for the create table. By default it is a + * noop. This is necessary because MySQL has a auto default value for the TIMESTAMP type that required a default + * value otherwise it would stick in the current date automagically. + */ + private void appendCanBeNull(StringBuilder sb, FieldType fieldType) { + // default is a noop + } + + /** + * Add SQL to handle a unique=true field. THis is not for uniqueCombo=true. + */ + private void addSingleUnique(StringBuilder sb, FieldType fieldType, List additionalArgs, + List statementsAfter) { + StringBuilder alterSb = new StringBuilder(); + alterSb.append(" UNIQUE ("); + appendEscapedEntityName(alterSb, fieldType.getColumnName()); + alterSb.append(")"); + additionalArgs.add(alterSb.toString()); + } + + /** + * Conversion to/from the Boolean Java field as a number because some databases like the true/false. + */ + protected static class BooleanNumberFieldConverter extends BaseFieldConverter { + public SqlType getSqlType() { + return SqlType.BOOLEAN; + } + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + boolean bool = (boolean) Boolean.parseBoolean(defaultStr); + return (bool ? Byte.valueOf((byte) 1) : Byte.valueOf((byte) 0)); + } + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + Boolean bool = (Boolean) obj; + return (bool ? Byte.valueOf((byte) 1) : Byte.valueOf((byte) 0)); + } + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getByte(columnPos); + } + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + byte arg = (Byte) sqlArg; + return (arg == 1 ? (Boolean) true : (Boolean) false); + } + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) { + return sqlArgToJava(fieldType, Byte.parseByte(stringValue), columnPos); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/db/BaseSqliteDatabaseType.java b/MemeProject/app/src/main/java/com/j256/ormlite/db/BaseSqliteDatabaseType.java new file mode 100644 index 0000000..4da921a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/db/BaseSqliteDatabaseType.java @@ -0,0 +1,91 @@ +package com.j256.ormlite.db; + +import java.util.List; + +import com.j256.ormlite.field.DataPersister; +import com.j256.ormlite.field.FieldConverter; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.field.types.BigDecimalStringType; + +/** + * Sqlite database type information used to create the tables, etc.. + * + *

+ * NOTE: We need this here because the Android and JDBC versions both subclasses it. + *

+ * + * @author graywatson + */ +public abstract class BaseSqliteDatabaseType extends BaseDatabaseType { + + private final static FieldConverter booleanConverter = new BooleanNumberFieldConverter(); + + @Override + protected void appendLongType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + /* + * This is unfortunate. SQLIte requires that a generated-id have the string "INTEGER PRIMARY KEY AUTOINCREMENT" + * even though the maximum generated value is 64-bit. See configureGeneratedId below. + */ + if (fieldType.getSqlType() == SqlType.LONG && fieldType.isGeneratedId()) { + sb.append("INTEGER"); + } else { + sb.append("BIGINT"); + } + } + + @Override + protected void configureGeneratedId(String tableName, StringBuilder sb, FieldType fieldType, + List statementsBefore, List statementsAfter, List additionalArgs, + List queriesAfter) { + /* + * Even though the documentation talks about INTEGER, it is 64-bit with a maximum value of 9223372036854775807. + * See http://www.sqlite.org/faq.html#q1 and http://www.sqlite.org/autoinc.html + */ + if (fieldType.getSqlType() != SqlType.INTEGER && fieldType.getSqlType() != SqlType.LONG) { + throw new IllegalArgumentException( + "Sqlite requires that auto-increment generated-id be integer or long type"); + } + sb.append("PRIMARY KEY AUTOINCREMENT "); + // no additional call to configureId here + } + + @Override + protected boolean generatedIdSqlAtEnd() { + return false; + } + + @Override + public boolean isVarcharFieldWidthSupported() { + return false; + } + + @Override + public boolean isCreateTableReturnsZero() { + // 'CREATE TABLE' statements seem to return 1 for some reason + return false; + } + + @Override + public boolean isCreateIfNotExistsSupported() { + return true; + } + + @Override + public FieldConverter getFieldConverter(DataPersister dataPersister, FieldType fieldType) { + // we are only overriding certain types + switch (dataPersister.getSqlType()) { + case BOOLEAN : + return booleanConverter; + case BIG_DECIMAL : + return BigDecimalStringType.getSingleton(); + default : + return super.getFieldConverter(dataPersister, fieldType); + } + } + + @Override + public void appendInsertNoColumns(StringBuilder sb) { + sb.append("DEFAULT VALUES"); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/db/DatabaseType.java b/MemeProject/app/src/main/java/com/j256/ormlite/db/DatabaseType.java new file mode 100644 index 0000000..12c08f0 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/db/DatabaseType.java @@ -0,0 +1,245 @@ +package com.j256.ormlite.db; + +import java.sql.Driver; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.field.DataPersister; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldConverter; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.DatabaseTableConfig; + +/** + * Definition of the per-database functionality needed to isolate the differences between the various databases. + * + * @author graywatson + */ +public interface DatabaseType { + + /** + * Return true if the database URL corresponds to this database type. Usually the URI is in the form jdbc:ddd:... + * where ddd is the driver url part. + */ + public boolean isDatabaseUrlThisType(String url, String dbTypePart); + + /** + * Load the driver class associated with this database so it can wire itself into JDBC. + * + * @throws SQLException + * If the driver class is not available in the classpath. + */ + public void loadDriver() throws SQLException; + + /** + * Set the driver instance on the database type. + */ + public void setDriver(Driver driver); + + /** + * Takes a {@link FieldType} and appends the SQL necessary to create the field to the string builder. The field may + * also generate additional arguments which go at the end of the insert statement or additional statements to be + * executed before or afterwards depending on the configurations. The database can also add to the list of queries + * that will be performed afterward to test portions of the config. + */ + public void appendColumnArg(String tableName, StringBuilder sb, FieldType fieldType, List additionalArgs, + List statementsBefore, List statementsAfter, List queriesAfter) throws SQLException; + + /** + * Appends information about primary key field(s) to the additional-args or other lists. + */ + public void addPrimaryKeySql(FieldType[] fieldTypes, List additionalArgs, List statementsBefore, + List statementsAfter, List queriesAfter) throws SQLException; + + /** + * Appends information about unique field(s) to the additional-args or other lists. + */ + public void addUniqueComboSql(FieldType[] fieldTypes, List additionalArgs, List statementsBefore, + List statementsAfter, List queriesAfter) throws SQLException; + + /** + * Takes a {@link FieldType} and adds the necessary statements to the before and after lists necessary so that the + * dropping of the table will succeed and will clear other associated sequences or other database artifacts + */ + public void dropColumnArg(FieldType fieldType, List statementsBefore, List statementsAfter); + + /** + * Add a entity-name word to the string builder wrapped in the proper characters to escape it. This avoids problems + * with table, column, and sequence-names being reserved words. + */ + public void appendEscapedEntityName(StringBuilder sb, String word); + + /** + * Add the word to the string builder wrapped in the proper characters to escape it. This avoids problems with data + * values being reserved words. + */ + public void appendEscapedWord(StringBuilder sb, String word); + + /** + * Return the name of an ID sequence based on the tabelName and the fieldType of the id. + */ + public String generateIdSequenceName(String tableName, FieldType idFieldType); + + /** + * Return the prefix to put at the front of a SQL line to mark it as a comment. + */ + public String getCommentLinePrefix(); + + /** + * Return true if the database needs a sequence when you use generated IDs. Some databases (H2, MySQL) create them + * auto-magically. This also means that the database needs to query for a sequence value before the object is + * inserted. For old[er] versions of Postgres, for example, the JDBC call-back stuff to get the just-inserted id + * value does not work so we have to get the next sequence value by hand, assign it into the object, and then insert + * the object -- yes two SQL statements. + */ + public boolean isIdSequenceNeeded(); + + /** + * Return the DataPersister to associate with the DataType. This allows the database instance to convert a field as + * necessary before it goes to the database. + */ + public DataPersister getDataPersister(DataPersister defaultPersister, FieldType fieldType); + + /** + * Return the FieldConverter to associate with the DataType. This allows the database instance to convert a field as + * necessary before it goes to the database. + */ + public FieldConverter getFieldConverter(DataPersister dataType, FieldType fieldType); + + /** + * Return true if the database supports the width parameter on VARCHAR fields. + */ + public boolean isVarcharFieldWidthSupported(); + + /** + * Return true if the database supports the LIMIT SQL command. Otherwise we have to use the + * {@link PreparedStatement#setMaxRows} instead. See prepareSqlStatement in MappedPreparedQuery. + */ + public boolean isLimitSqlSupported(); + + /** + * Return true if the LIMIT should be called after SELECT otherwise at the end of the WHERE (the default). + */ + public boolean isLimitAfterSelect(); + + /** + * Append to the string builder the necessary SQL to limit the results to a certain number. With some database + * types, the offset is an argument to the LIMIT so the offset value (which could be null or not) is passed in. The + * database type can choose to ignore it. + */ + public void appendLimitValue(StringBuilder sb, long limit, Long offset); + + /** + * Return true if the database supports the OFFSET SQL command in some form. + */ + public boolean isOffsetSqlSupported(); + + /** + * Return true if the database supports the offset as a comma argument from the limit. This also means that the + * limit _must_ be specified if the offset is specified + */ + public boolean isOffsetLimitArgument(); + + /** + * Append to the string builder the necessary SQL to start the results at a certain row number. + */ + public void appendOffsetValue(StringBuilder sb, long offset); + + /** + * Append the SQL necessary to get the next-value from a sequence. This is only necessary if + * {@link #isIdSequenceNeeded} is true. + */ + public void appendSelectNextValFromSequence(StringBuilder sb, String sequenceName); + + /** + * Append the SQL necessary to properly finish a CREATE TABLE line. + */ + public void appendCreateTableSuffix(StringBuilder sb); + + /** + * Returns true if a 'CREATE TABLE' statement should return 0. False if > 0. + */ + public boolean isCreateTableReturnsZero(); + + /** + * Returns true if CREATE and DROP TABLE statements can return < 0 and still have worked. Gross! + */ + public boolean isCreateTableReturnsNegative(); + + /** + * Returns true if table and field names should be made uppercase. + * + *

+ * Turns out that Derby and Hsqldb are doing something wrong (IMO) with entity names. If you create a table with the + * name "footable" (with the quotes) then it will be created as lowercase footable, case sensitive. However, if you + * then issue the query 'select * from footable' (without quotes) it won't find the table because it gets promoted + * to be FOOTABLE and is searched in a case sensitive manner. So for these databases, entity names have to be forced + * to be uppercase so external queries will also work. + *

+ */ + public boolean isEntityNamesMustBeUpCase(); + + /** + * Returns true if nested savePoints are supported, otherwise false. + */ + public boolean isNestedSavePointsSupported(); + + /** + * Return an statement that doesn't do anything but which can be used to ping the database by sending it over a + * database connection. + */ + public String getPingStatement(); + + /** + * Returns true if batch operations should be done inside of a transaction. Default is false in which case + * auto-commit disabling will be done. + */ + public boolean isBatchUseTransaction(); + + /** + * Returns true if the table truncate operation is supported. + */ + public boolean isTruncateSupported(); + + /** + * Returns true if the table creation IF NOT EXISTS syntax is supported. + */ + public boolean isCreateIfNotExistsSupported(); + + /** + * Does the database support the "CREATE INDEX IF NOT EXISTS" SQL construct. By default this just calls + * {@link #isCreateIfNotExistsSupported()}. + */ + public boolean isCreateIndexIfNotExistsSupported(); + + /** + * Returns true if we have to select the value of the sequence before we insert a new data row. + */ + public boolean isSelectSequenceBeforeInsert(); + + /** + * Does the database support the {@link DatabaseField#allowGeneratedIdInsert()} setting which allows people to + * insert values into generated-id columns. + */ + public boolean isAllowGeneratedIdInsertSupported(); + + /** + * Return the name of the database for logging purposes. + */ + public String getDatabaseName(); + + /** + * Extract and return a custom database configuration for this class. + * + * @return null if no custom configuration extractor for this database type. + */ + public DatabaseTableConfig extractDatabaseTableConfig(ConnectionSource connectionSource, Class clazz) + throws SQLException; + + /** + * Append the SQL necessary to properly finish a "INSERT INTO xxx" line when there are no arguments. + */ + public void appendInsertNoColumns(StringBuilder sb); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/db/SqliteAndroidDatabaseType.java b/MemeProject/app/src/main/java/com/j256/ormlite/db/SqliteAndroidDatabaseType.java new file mode 100644 index 0000000..a29dbde --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/db/SqliteAndroidDatabaseType.java @@ -0,0 +1,86 @@ +package com.j256.ormlite.db; + +import java.sql.SQLException; + +import com.j256.ormlite.android.DatabaseTableConfigUtil; +import com.j256.ormlite.field.DataPersister; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.types.DateStringType; +import com.j256.ormlite.field.types.TimeStampStringType; +import com.j256.ormlite.field.types.TimeStampType; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.DatabaseTableConfig; + +/** + * Sqlite database type information for the Android OS that makes native calls to the Android OS database APIs. + * + * @author graywatson + */ +public class SqliteAndroidDatabaseType extends BaseSqliteDatabaseType { + + @Override + public void loadDriver() { + // noop + } + + public boolean isDatabaseUrlThisType(String url, String dbTypePart) { + // not used by the android code + return true; + } + + @Override + protected String getDriverClassName() { + // no driver to load in android-land + return null; + } + + public String getDatabaseName() { + return "Android SQLite"; + } + + @Override + protected void appendDateType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + // default is to store the date as a string + appendStringType(sb, fieldType, fieldWidth); + } + + @Override + protected void appendBooleanType(StringBuilder sb, FieldType fieldType, int fieldWidth) { + // we have to convert booleans to numbers + appendShortType(sb, fieldType, fieldWidth); + } + + @Override + public DataPersister getDataPersister(DataPersister defaultPersister, FieldType fieldType) { + if (defaultPersister == null) { + return super.getDataPersister(defaultPersister, fieldType); + } + // we are only overriding certain types + switch (defaultPersister.getSqlType()) { + case DATE : + if (defaultPersister instanceof TimeStampType) { + return TimeStampStringType.getSingleton(); + } else { + return DateStringType.getSingleton(); + } + default : + return super.getDataPersister(defaultPersister, fieldType); + } + } + + @Override + public boolean isNestedSavePointsSupported() { + return false; + } + + @Override + public boolean isBatchUseTransaction() { + return true; + } + + @Override + public DatabaseTableConfig extractDatabaseTableConfig(ConnectionSource connectionSource, Class clazz) + throws SQLException { + return DatabaseTableConfigUtil.fromClass(connectionSource, clazz); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/BaseFieldConverter.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/BaseFieldConverter.java new file mode 100644 index 0000000..3de768e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/BaseFieldConverter.java @@ -0,0 +1,44 @@ +package com.j256.ormlite.field; + +import java.sql.SQLException; + +import com.j256.ormlite.support.DatabaseResults; + +/** + * Base class for field-converters. + * + * @author graywatson + */ +public abstract class BaseFieldConverter implements FieldConverter { + + /** + * @throws SQLException + * If there are problems with the conversion. + */ + public Object javaToSqlArg(FieldType fieldType, Object javaObject) throws SQLException { + // noop pass-thru + return javaObject; + } + + public Object resultToJava(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + Object value = resultToSqlArg(fieldType, results, columnPos); + if (value == null) { + return null; + } else { + return sqlArgToJava(fieldType, value, columnPos); + } + } + + /** + * @throws SQLException + * If there are problems with the conversion. + */ + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + // noop pass-thru + return sqlArg; + } + + public boolean isStreamType() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersister.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersister.java new file mode 100644 index 0000000..65ee348 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersister.java @@ -0,0 +1,119 @@ +package com.j256.ormlite.field; + +import java.lang.reflect.Field; +import java.sql.SQLException; + +import com.j256.ormlite.field.types.BaseDataType; +import com.j256.ormlite.stmt.ArgumentHolder; + +/** + * Data type that provide Java class to/from database mapping. + * + *

+ * If you are defining your own custom persister, then chances are you should extend {@link BaseDataType}. See + * {@link DatabaseField#persisterClass()}. + *

+ * + * @author graywatson + */ +public interface DataPersister extends FieldConverter { + + /** + * Return the classes that should be associated with this. + */ + public Class[] getAssociatedClasses(); + + /** + * Return the class names that should be associated with this or null. This is used by reflection classes so we can + * discover if a Field matches _without_ needed the class dependency in -core. + */ + public String[] getAssociatedClassNames(); + + /** + * This makes a configuration object for the data-type or returns null if none. The object can be accessed later via + * {@link FieldType#getDataTypeConfigObj()}. + */ + public Object makeConfigObject(FieldType fieldType) throws SQLException; + + /** + * Convert a {@link Number} object to its primitive object suitable for assigning to an ID field. + */ + public Object convertIdNumber(Number number); + + /** + * Return true if this type can be auto-generated by the database. Probably only numbers will return true. + */ + public boolean isValidGeneratedType(); + + /** + * Return true if the field is appropriate for this persister otherwise false. + */ + public boolean isValidForField(Field field); + + /** + * Return the class most associated with this persister or null if none. + */ + public Class getPrimaryClass(); + + /** + * Return whether this field's default value should be escaped in SQL. + */ + public boolean isEscapedDefaultValue(); + + /** + * Return whether we need to escape this value in SQL expressions. Numbers _must_ not be escaped but most other + * values should be. + */ + public boolean isEscapedValue(); + + /** + * Return whether this field is a primitive type or not. This is used to know if we should throw if the field value + * is null. + */ + public boolean isPrimitive(); + + /** + * Return true if this data type be compared in SQL statements. + */ + public boolean isComparable(); + + /** + * Return true if this data type can be an id column in a class. + */ + public boolean isAppropriateId(); + + /** + * Must use {@link ArgumentHolder} when querying for values of this type. + */ + public boolean isArgumentHolderRequired(); + + /** + * Return true if this type creates its own generated ids else false to have the database do it. + */ + public boolean isSelfGeneratedId(); + + /** + * Return a generated id if appropriate or null if none. + */ + public Object generateId(); + + /** + * Return the default width associated with this type or 0 if none. + */ + public int getDefaultWidth(); + + /** + * Compare two fields of this type returning true if equals else false. + */ + public boolean dataIsEqual(Object obj1, Object obj2); + + /** + * Return true if this is a valid field for the {@link DatabaseField#version()}. + */ + public boolean isValidForVersion(); + + /** + * Move the current-value to the next value. Used for the version field. + */ + public Object moveToNextValue(Object currentValue) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersisterManager.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersisterManager.java new file mode 100644 index 0000000..9f049b4 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/DataPersisterManager.java @@ -0,0 +1,112 @@ +package com.j256.ormlite.field; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.j256.ormlite.field.types.EnumStringType; + +/** + * Class used to manage the various data types used by the system. The bulk of the data types come from the + * {@link DataType} enumerated fields although you can also register your own here using the + * {@link #registerDataPersisters(DataPersister...)} method. + * + * @author graywatson + */ +public class DataPersisterManager { + + private static final DataPersister DEFAULT_ENUM_PERSISTER = EnumStringType.getSingleton(); + + private static final Map builtInMap; + private static List registeredPersisters = null; + + static { + // add our built-in persisters + builtInMap = new HashMap(); + for (DataType dataType : DataType.values()) { + DataPersister persister = dataType.getDataPersister(); + if (persister != null) { + for (Class clazz : persister.getAssociatedClasses()) { + builtInMap.put(clazz.getName(), persister); + } + String[] associatedClassNames = persister.getAssociatedClassNames(); + if (associatedClassNames != null) { + for (String className : persister.getAssociatedClassNames()) { + builtInMap.put(className, persister); + } + } + } + } + } + + private DataPersisterManager() { + // only for static methods + } + + /** + * Register a data type with the manager. + */ + public static void registerDataPersisters(DataPersister... dataPersisters) { + // we build the map and replace it to lower the chance of concurrency issues + List newList = new ArrayList(); + if (registeredPersisters != null) { + newList.addAll(registeredPersisters); + } + for (DataPersister persister : dataPersisters) { + newList.add(persister); + } + registeredPersisters = newList; + } + + /** + * Remove any previously persisters that were registered with {@link #registerDataPersisters(DataPersister...)}. + */ + public static void clear() { + registeredPersisters = null; + } + + /** + * Lookup the data-type associated with the class. + * + * @return The associated data-type interface or null if none found. + */ + public static DataPersister lookupForField(Field field) { + + // see if the any of the registered persisters are valid first + if (registeredPersisters != null) { + for (DataPersister persister : registeredPersisters) { + if (persister.isValidForField(field)) { + return persister; + } + // check the classes instead + for (Class clazz : persister.getAssociatedClasses()) { + if (field.getType() == clazz) { + return persister; + } + } + } + } + + // look it up in our built-in map by class + DataPersister dataPersister = builtInMap.get(field.getType().getName()); + if (dataPersister != null) { + return dataPersister; + } + + /* + * Special case for enum types. We can't put this in the registered persisters because we want people to be able + * to override it. + */ + if (field.getType().isEnum()) { + return DEFAULT_ENUM_PERSISTER; + } else { + /* + * Serializable classes return null here because we don't want them to be automatically configured for + * forwards compatibility with future field types that happen to be Serializable. + */ + return null; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/DataType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/DataType.java new file mode 100644 index 0000000..fae9901 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/DataType.java @@ -0,0 +1,241 @@ +package com.j256.ormlite.field; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.j256.ormlite.field.types.BigDecimalNumericType; +import com.j256.ormlite.field.types.BigDecimalStringType; +import com.j256.ormlite.field.types.BigIntegerType; +import com.j256.ormlite.field.types.BooleanCharType; +import com.j256.ormlite.field.types.BooleanIntegerType; +import com.j256.ormlite.field.types.BooleanObjectType; +import com.j256.ormlite.field.types.BooleanType; +import com.j256.ormlite.field.types.ByteArrayType; +import com.j256.ormlite.field.types.ByteObjectType; +import com.j256.ormlite.field.types.ByteType; +import com.j256.ormlite.field.types.CharType; +import com.j256.ormlite.field.types.CharacterObjectType; +import com.j256.ormlite.field.types.DateLongType; +import com.j256.ormlite.field.types.DateStringType; +import com.j256.ormlite.field.types.DateTimeType; +import com.j256.ormlite.field.types.DateType; +import com.j256.ormlite.field.types.DoubleObjectType; +import com.j256.ormlite.field.types.DoubleType; +import com.j256.ormlite.field.types.EnumIntegerType; +import com.j256.ormlite.field.types.EnumStringType; +import com.j256.ormlite.field.types.FloatObjectType; +import com.j256.ormlite.field.types.FloatType; +import com.j256.ormlite.field.types.IntType; +import com.j256.ormlite.field.types.IntegerObjectType; +import com.j256.ormlite.field.types.LongObjectType; +import com.j256.ormlite.field.types.LongStringType; +import com.j256.ormlite.field.types.LongType; +import com.j256.ormlite.field.types.SerializableType; +import com.j256.ormlite.field.types.ShortObjectType; +import com.j256.ormlite.field.types.ShortType; +import com.j256.ormlite.field.types.SqlDateType; +import com.j256.ormlite.field.types.StringBytesType; +import com.j256.ormlite.field.types.StringType; +import com.j256.ormlite.field.types.TimeStampType; +import com.j256.ormlite.field.types.UuidType; + +/** + * Data type enumeration that corresponds to a {@link DataPersister}. + * + * @author graywatson + */ +public enum DataType { + + /** + * Persists the {@link String} Java class. + */ + STRING(StringType.getSingleton()), + /** + * Persists the {@link String} Java class. + */ + LONG_STRING(LongStringType.getSingleton()), + /** + * Persists the {@link String} Java class as an array of bytes. By default this will use {@link #STRING} so you will + * need to specify this using {@link DatabaseField#dataType()}. + */ + STRING_BYTES(StringBytesType.getSingleton()), + /** + * Persists the boolean Java primitive. + */ + BOOLEAN(BooleanType.getSingleton()), + /** + * Persists the {@link Boolean} object Java class. + */ + BOOLEAN_OBJ(BooleanObjectType.getSingleton()), + /** + * Persists the boolean Java primitive as a character in the database. + */ + BOOLEAN_CHAR(BooleanCharType.getSingleton()), + /** + * Persists the boolean Java primitive as an integer in the database. + */ + BOOLEAN_INTEGER(BooleanIntegerType.getSingleton()), + /** + * Persists the {@link java.util.Date} Java class. + * + *

+ * NOTE: This is not the same as the {@link java.sql.Date} class. + *

+ */ + DATE(DateType.getSingleton()), + + /** + * Persists the {@link java.util.Date} Java class as long milliseconds since epoch. By default this will use + * {@link #DATE} so you will need to specify this using {@link DatabaseField#dataType()}. + * + *

+ * NOTE: This is not the same as the {@link java.sql.Date} class. + *

+ */ + DATE_LONG(DateLongType.getSingleton()), + /** + * Persists the {@link java.util.Date} Java class as a string of a format. By default this will use {@link #DATE} so + * you will need to specify this using {@link DatabaseField#dataType()}. + * + *

+ * NOTE: This is not the same as the {@link java.sql.Date} class. + *

+ * + *

+ * WARNING: Because of SimpleDateFormat not being reentrant, this has to do some synchronization with every + * data in/out unfortunately. + *

+ */ + DATE_STRING(DateStringType.getSingleton()), + /** + * Persists the char primitive. + */ + CHAR(CharType.getSingleton()), + /** + * Persists the {@link Character} object Java class. + */ + CHAR_OBJ(CharacterObjectType.getSingleton()), + /** + * Persists the byte primitive. + */ + BYTE(ByteType.getSingleton()), + /** + * Persists the byte[] array type. Because of some backwards compatibility issues, you will need to specify this + * using {@link DatabaseField#dataType()}. It won't be detected automatically. + */ + BYTE_ARRAY(ByteArrayType.getSingleton()), + /** + * Persists the {@link Byte} object Java class. + */ + BYTE_OBJ(ByteObjectType.getSingleton()), + /** + * Persists the short primitive. + */ + SHORT(ShortType.getSingleton()), + /** + * Persists the {@link Short} object Java class. + */ + SHORT_OBJ(ShortObjectType.getSingleton()), + /** + * Persists the int primitive. + */ + INTEGER(IntType.getSingleton()), + /** + * Persists the {@link Integer} object Java class. + */ + INTEGER_OBJ(IntegerObjectType.getSingleton()), + /** + * Persists the long primitive. + */ + LONG(LongType.getSingleton()), + /** + * Persists the {@link Long} object Java class. + */ + LONG_OBJ(LongObjectType.getSingleton()), + /** + * Persists the float primitive. + */ + FLOAT(FloatType.getSingleton()), + /** + * Persists the {@link Float} object Java class. + */ + FLOAT_OBJ(FloatObjectType.getSingleton()), + /** + * Persists the double primitive. + */ + DOUBLE(DoubleType.getSingleton()), + /** + * Persists the {@link Double} object Java class. + */ + DOUBLE_OBJ(DoubleObjectType.getSingleton()), + /** + * Persists an unknown Java Object that is serializable. Because of some backwards and forwards compatibility + * concerns, you will need to specify this using {@link DatabaseField#dataType()}. It won't be detected + * automatically. + */ + SERIALIZABLE(SerializableType.getSingleton()), + /** + * Persists an Enum Java class as its string value. You can also specify the {@link #ENUM_INTEGER} as the type. + */ + ENUM_STRING(EnumStringType.getSingleton()), + /** + * Persists an Enum Java class as its ordinal integer value. You can also specify the {@link #ENUM_STRING} as the + * type. + */ + ENUM_INTEGER(EnumIntegerType.getSingleton()), + /** + * Persists the {@link java.util.UUID} Java class. + */ + UUID(UuidType.getSingleton()), + /** + * Persists the {@link java.util.UUID} Java class as a native UUID column which is only supported by a couple of + * database types. + */ + UUID_NATIVE(UuidType.getSingleton()), + /** + * Persists the {@link BigInteger} Java class. + */ + BIG_INTEGER(BigIntegerType.getSingleton()), + /** + * Persists the {@link BigDecimal} Java class as a String. + */ + BIG_DECIMAL(BigDecimalStringType.getSingleton()), + /** + * Persists the {@link BigDecimal} Java class as a SQL NUMERIC. + */ + BIG_DECIMAL_NUMERIC(BigDecimalNumericType.getSingleton()), + /** + * Persists the org.joda.time.DateTime type with reflection since we don't want to add the dependency. Because this + * class uses reflection, you have to specify this using {@link DatabaseField#dataType()}. It won't be detected + * automatically. + */ + DATE_TIME(DateTimeType.getSingleton()), + /** + * Persists the {@link java.sql.Date} Java class. + * + *

+ * NOTE: If you want to use the {@link java.util.Date} class then use {@link #DATE} which is recommended instead. + *

+ */ + SQL_DATE(SqlDateType.getSingleton()), + /** + * Persists the {@link java.sql.Timestamp} Java class. The {@link #DATE} type is recommended instead. + */ + TIME_STAMP(TimeStampType.getSingleton()), + /** + * Marker for fields that are unknown. + */ + UNKNOWN(null), + // end + ; + + private final DataPersister dataPersister; + + private DataType(DataPersister dataPersister) { + this.dataPersister = dataPersister; + } + + public DataPersister getDataPersister() { + return dataPersister; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseField.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseField.java new file mode 100644 index 0000000..fe9d03e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseField.java @@ -0,0 +1,310 @@ +package com.j256.ormlite.field; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.types.VoidType; + +/** + *

+ * Annotation that identifies a field in a class that corresponds to a column in the database and will be persisted. + * Fields that are not to be persisted such as transient or other temporary fields probably should be ignored. For + * example: + *

+ * + *
+ * @DatabaseField(id = true)
+ * private String name;
+ * 
+ * @DatabaseField(columnName = "passwd", canBeNull = false)
+ * private String password;
+ * 
+ * + *

+ * WARNING: If you add any extra fields here, you will need to add them to {@link DatabaseFieldConfig}, + * {@link DatabaseFieldConfigLoader}, DatabaseFieldConfigLoaderTest, and DatabaseTableConfigUtil as well. + *

+ * + * @author graywatson + */ +@Target(FIELD) +@Retention(RUNTIME) +public @interface DatabaseField { + + /** this special string is used as a .equals check to see if no default was specified */ + public static final String DEFAULT_STRING = "__ormlite__ no default value string was specified"; + + /** + * Default for the maxForeignAutoRefreshLevel. + * + * @see #maxForeignAutoRefreshLevel() + */ + public static final int DEFAULT_MAX_FOREIGN_AUTO_REFRESH_LEVEL = 2; + + /** + * The name of the column in the database. If not set then the name is taken from the field name. + */ + String columnName() default ""; + + /** + * The DataType associated with the field. If not set then the Java class of the field is used to match with the + * appropriate DataType. This should only be set if you are overriding the default database type or if the field + * cannot be automatically determined (ex: byte[]). + */ + DataType dataType() default DataType.UNKNOWN; + + /** + * The default value of the field for creating the table. Default is none. + * + *

+ * NOTE: If the field has a null value then this value will be inserted in its place when you call you call + * {@link Dao#create(Object)}. This does not apply to primitive fields so you should just assign them in the class + * instead. + *

+ */ + String defaultValue() default DEFAULT_STRING; + + /** + * Width of array fields (often for strings). Default is 0 which means to take the data-type and database-specific + * default. For strings that means 255 characters although some databases do not support this. + */ + int width() default 0; + + /** + * Whether the field can be assigned to null or have no value. Default is true. + */ + boolean canBeNull() default true; + + /** + * Whether the field is the id field or not. Default is false. Only one field can have this set in a class. If you + * don't have it set then you won't be able to use the query, update, and delete by ID methods. Only one of this, + * {@link #generatedId}, and {@link #generatedIdSequence} can be specified. + */ + boolean id() default false; + + /** + * Whether the field is an auto-generated id field. Default is false. With databases for which + * {@link DatabaseType#isIdSequenceNeeded} is true then this will cause the name of the sequence to be + * auto-generated. To specify the name of the sequence use {@link #generatedIdSequence}. Only one of this, + * {@link #id}, and {@link #generatedIdSequence} can be specified. + */ + boolean generatedId() default false; + + /** + * The name of the sequence number to be used to generate this value. Default is none. This is only necessary for + * database for which {@link DatabaseType#isIdSequenceNeeded} is true and you already have a defined sequence that + * you want to use. If you use {@link #generatedId} instead then the code will auto-generate a sequence name. Only + * one of this, {@link #id}, and {@link #generatedId} can be specified. + */ + String generatedIdSequence() default ""; + + /** + * Field is a non-primitive object that corresponds to another class that is also stored in the database. It must + * have an id field (either {@link #id}, {@link #generatedId}, or {@link #generatedIdSequence} which will be stored + * in this table. When an object is returned from a query call, any foreign objects will just have the id field set + * in it. To get all of the other fields you will have to do a refresh on the object using its own Dao. + */ + boolean foreign() default false; + + /** + *

+ * Package should use get...() and set...() to access the field value instead of the default direct field access via + * reflection. This may be necessary if the object you are storing has protections around it. + *

+ * + *

+ * NOTE: The name of the get method must match getXxx() where Xxx is the name of the field with the + * first letter capitalized. The get must return a class which matches the field's. The set method + * must match setXxx(), have a single argument whose class matches the field's, and return void. For example: + *

+ * + *
+	 * @DatabaseField
+	 * private Integer orderCount;
+	 * 
+	 * public Integer getOrderCount() {
+	 * 	return orderCount;
+	 * }
+	 * 
+	 * public void setOrderCount(Integer orderCount) {
+	 * 	this.orderCount = orderCount;
+	 * }
+	 * 
+ */ + boolean useGetSet() default false; + + /** + * If the field is an Enum and the database has a value that is not one of the names in the enum then this name will + * be used instead. It must match one of the enum names. This is mainly useful when you are worried about backwards + * compatibility with older database rows or future compatibility if you have to roll back to older data definition. + */ + String unknownEnumName() default ""; + + /** + * If this is set to true (default false) then it will throw a SQLException if a null value is attempted to be + * de-persisted into a primitive. This must only be used on a primitive field. If this is false then if the database + * field is null, the value of the primitive will be set to 0. + */ + boolean throwIfNull() default false; + + /** + * Set this to be false (default true) to not store this field in the database. This is useful if you want to have + * the annotation on all of your fields but turn off the writing of some of them to the database. + */ + boolean persisted() default true; + + /** + * Optional format information that can be used by various field types. For example, if the Date is to be persisted + * as a string, this can set what format string to use for the date. + */ + String format() default ""; + + /** + * Set this to be true (default false) to have the database insure that the column is unique to all rows in the + * table. Use this when you wan a field to be unique even if it is not the identify field. For example, if you have + * the firstName and lastName fields, both with unique=true and you have "Bob", "Smith" in the database, you cannot + * insert either "Bob", "Jones" or "Kevin", "Smith". + */ + boolean unique() default false; + + /** + * Set this to be true (default false) to have the database insure that _all_ of the columns marked with this as + * true will together be unique. For example, if you have the firstName and lastName fields, both with unique=true + * and you have "Bob", "Smith" in the database, you cannot insert another "Bob", "Smith" but you can insert "Bob", + * "Jones" and "Kevin", "Smith". + */ + boolean uniqueCombo() default false; + + /** + * Set this to be true (default false) to have the database add an index for this field. This will create an index + * with the name columnName + "_idx". To specify a specific name of the index or to index multiple fields, use + * {@link #indexName()}. + */ + boolean index() default false; + + /** + * Set this to be true (default false) to have the database add a unique index for this field. This is the same as + * the {@link #index()} field but this ensures that all of the values in the index are unique.. + */ + boolean uniqueIndex() default false; + + /** + * Set this to be a string (default none) to have the database add an index for this field with this name. You do + * not need to specify the {@link #index()} boolean as well. To index multiple fields together in one index, each of + * the fields should have the same indexName value. + */ + String indexName() default ""; + + /** + * Set this to be a string (default none) to have the database add a unique index for this field with this name. + * This is the same as the {@link #indexName()} field but this ensures that all of the values in the index are + * unique. + */ + String uniqueIndexName() default ""; + + /** + * Set this to be true (default false) to have a foreign field automagically refreshed when an object is queried. + * This will _not_ automagically create the foreign object but when the object is queried, a separate database call + * will be made to load of the fields of the foreign object via an internal DAO. The default is to just have the ID + * field in the object retrieved and for the caller to call refresh on the correct DAO. + */ + boolean foreignAutoRefresh() default false; + + /** + * Set this to be the number of times to refresh a foreign object's foreign object. If you query for A and it has an + * foreign field B which has an foreign field C ..., then querying for A could get expensive. Setting this value to + * 1 will mean that when you query for A, B will be auto-refreshed, but C will just have its id field set. This also + * works if A has an auto-refresh field B which has an auto-refresh field A. + * + *

+ * NOTE: Increasing this value will result in more database transactions whenever you query for A, so use + * carefully. + *

+ */ + int maxForeignAutoRefreshLevel() default DEFAULT_MAX_FOREIGN_AUTO_REFRESH_LEVEL; + + /** + * Allows you to set a custom persister class to handle this field. This class must have a getSingleton() static + * method defined which will return the singleton persister. + * + * @see DataPersister + */ + Class persisterClass() default VoidType.class; + + /** + * If this is set to true then inserting an object with the ID field already set (i.e. not null, 0) will not + * override it with a generated-id. If the field is null or 0 then the id will be generated. This is useful when you + * have a table where items sometimes have IDs and sometimes need them generated. This only works if the database + * supports this behavior and if {@link #generatedId()} is also true for the field. + */ + boolean allowGeneratedIdInsert() default false; + + /** + * Specify the SQL necessary to create this field in the database. This can be used if you need to tune the schema + * to enable some per-database feature or to override the default SQL generated. + */ + String columnDefinition() default ""; + + /** + *

+ * Set this to be true (default false) to have the foreign field will be automagically created using its internal + * DAO if the ID field is not set (null or 0). So when you call dao.create() on the parent object, any field with + * this set to true will possibly be created via an internal DAO. By default you have to create the object using its + * DAO directly. This only works if {@link #generatedId()} is also set to true. + *

+ * + *
+	 * Order order1 = new Order();
+	 * // account1 has not been created in the db yet and it's id == null
+	 * order1.account = account1;
+	 * // this will create order1 _and_ pass order1.account to the internal account dao.create().
+	 * orderDao.create(order1);
+	 * 
+ */ + boolean foreignAutoCreate() default false; + + /** + * Set this to be true (default false) to have this field to be a version field similar to + * javax.persistence.Version. When an update is done on a row the following happens: + * + *
    + *
  • The update statement is augmented with a "WHERE version = current-value"
  • + *
  • The new value being updated is the current-value + 1 or the current Date
  • + *
  • If the row has been updated by another entity then the update will not change the row and 0 rows changed will + * be returned.
  • + *
  • If a row was changed then the object is changed so the version field gets the new value
  • + *
+ * + * The field should be a short, integer, long, Date, Date-string, or Date-long type. + */ + boolean version() default false; + + /** + * Name of the foreign object's field that is tied to this table. This does not need to be specified if you are + * using the ID of the foreign object which is recommended. For example, if you have an Order object with a foreign + * Account then you may want to key off of the Account name instead of the Account ID. + * + *

+ * NOTE: Setting this implies {@link #foreignAutoRefresh()} is also set to true because there is no way to + * refresh the object since the id field is not stored in the database. So when this is set, the field will be + * automatically refreshed in another database query. + *

+ */ + String foreignColumnName() default ""; + + /** + * Set this to be true (default false) if this field is a read-only field. This field will be returned by queries + * however it will be ignored during insert/create statements. + */ + boolean readOnly() default false; + + /* + * NOTE to developers: if you add fields here you have to add them to the DatabaseFieldConfig, + * DatabaseFieldConfigLoader, DatabaseFieldConfigLoaderTest, and DatabaseTableConfigUtil. + */ +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfig.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfig.java new file mode 100644 index 0000000..f88fb0c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfig.java @@ -0,0 +1,791 @@ +package com.j256.ormlite.field; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.util.Locale; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.types.VoidType; +import com.j256.ormlite.misc.JavaxPersistenceConfigurer; +import com.j256.ormlite.table.DatabaseTableConfig; + +/** + * Database field configuration information either supplied by a {@link DatabaseField} annotation or by direct Java or + * Spring wiring. + * + * @author graywatson + */ +public class DatabaseFieldConfig { + + public static final Class DEFAULT_PERSISTER_CLASS = VoidType.class; + public static final DataType DEFAULT_DATA_TYPE = DataType.UNKNOWN; + public static final boolean DEFAULT_CAN_BE_NULL = true; + public static final boolean DEFAULT_FOREIGN_COLLECTION_ORDER_ASCENDING = true; + public static final int NO_MAX_FOREIGN_AUTO_REFRESH_LEVEL_SPECIFIED = -1; + + private static final int DEFAULT_MAX_EAGER_FOREIGN_COLLECTION_LEVEL = + ForeignCollectionField.DEFAULT_MAX_EAGER_LEVEL; + + private static JavaxPersistenceConfigurer javaxPersistenceConfigurer; + + private String fieldName; + private String columnName; + private DataType dataType = DEFAULT_DATA_TYPE; + private DataPersister dataPersister; + private String defaultValue; + private int width; + private boolean canBeNull = DEFAULT_CAN_BE_NULL; + private boolean id; + private boolean generatedId; + private String generatedIdSequence; + private boolean foreign; + private DatabaseTableConfig foreignTableConfig; + private boolean useGetSet; + private Enum unknownEnumValue; + private boolean throwIfNull; + private boolean persisted = true; + private String format; + private boolean unique; + private boolean uniqueCombo; + private boolean index; + private String indexName; + private boolean uniqueIndex; + private String uniqueIndexName; + private boolean foreignAutoRefresh; + private int maxForeignAutoRefreshLevel = NO_MAX_FOREIGN_AUTO_REFRESH_LEVEL_SPECIFIED; + private Class persisterClass = DEFAULT_PERSISTER_CLASS; + private boolean allowGeneratedIdInsert; + private String columnDefinition; + private boolean foreignAutoCreate; + private boolean version; + private String foreignColumnName; + private boolean readOnly; + // foreign collection field information + private boolean foreignCollection; + private boolean foreignCollectionEager; + private int foreignCollectionMaxEagerLevel = DEFAULT_MAX_EAGER_FOREIGN_COLLECTION_LEVEL; + private String foreignCollectionColumnName; + private String foreignCollectionOrderColumnName; + private boolean foreignCollectionOrderAscending = DEFAULT_FOREIGN_COLLECTION_ORDER_ASCENDING; + private String foreignCollectionForeignFieldName; + + static { + try { + // see if we have this class at runtime + Class.forName("javax.persistence.Entity"); + // if we do then get our JavaxPersistance class + Class clazz = Class.forName("com.j256.ormlite.misc.JavaxPersistenceImpl"); + javaxPersistenceConfigurer = (JavaxPersistenceConfigurer) clazz.getConstructor().newInstance(); + } catch (Exception e) { + // no configurer + javaxPersistenceConfigurer = null; + } + } + + public DatabaseFieldConfig() { + // for spring + } + + public DatabaseFieldConfig(String fieldName) { + this.fieldName = fieldName; + } + + public DatabaseFieldConfig(String fieldName, String columnName, DataType dataType, String defaultValue, int width, + boolean canBeNull, boolean id, boolean generatedId, String generatedIdSequence, boolean foreign, + DatabaseTableConfig foreignTableConfig, boolean useGetSet, Enum unknownEnumValue, + boolean throwIfNull, String format, boolean unique, String indexName, String uniqueIndexName, + boolean autoRefresh, int maxForeignAutoRefreshLevel, int maxForeignCollectionLevel) { + this.fieldName = fieldName; + this.columnName = columnName; + this.dataType = DataType.UNKNOWN; + this.defaultValue = defaultValue; + this.width = width; + this.canBeNull = canBeNull; + this.id = id; + this.generatedId = generatedId; + this.generatedIdSequence = generatedIdSequence; + this.foreign = foreign; + this.foreignTableConfig = foreignTableConfig; + this.useGetSet = useGetSet; + this.unknownEnumValue = unknownEnumValue; + this.throwIfNull = throwIfNull; + this.format = format; + this.unique = unique; + this.indexName = indexName; + this.uniqueIndexName = uniqueIndexName; + this.foreignAutoRefresh = autoRefresh; + this.maxForeignAutoRefreshLevel = maxForeignAutoRefreshLevel; + this.foreignCollectionMaxEagerLevel = maxForeignCollectionLevel; + } + + /** + * Return the name of the field in the class. + */ + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + /** + * @see DatabaseField#columnName() + */ + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + /** + * @see DatabaseField#dataType() + */ + public DataType getDataType() { + return dataType; + } + + public void setDataType(DataType dataType) { + this.dataType = dataType; + } + + /* + * The name is historical. + */ + public DataPersister getDataPersister() { + if (dataPersister == null) { + return dataType.getDataPersister(); + } else { + return dataPersister; + } + } + + /** + * The name is historical. + */ + public void setDataPersister(DataPersister dataPersister) { + this.dataPersister = dataPersister; + } + + /** + * @see DatabaseField#defaultValue() + */ + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * @see DatabaseField#width() + */ + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + /** + * @see DatabaseField#canBeNull() + */ + public boolean isCanBeNull() { + return canBeNull; + } + + public void setCanBeNull(boolean canBeNull) { + this.canBeNull = canBeNull; + } + + /** + * @see DatabaseField#id() + */ + public boolean isId() { + return id; + } + + public void setId(boolean id) { + this.id = id; + } + + /** + * @see DatabaseField#generatedId() + */ + public boolean isGeneratedId() { + return generatedId; + } + + public void setGeneratedId(boolean generatedId) { + this.generatedId = generatedId; + } + + /** + * @see DatabaseField#generatedIdSequence() + */ + public String getGeneratedIdSequence() { + return generatedIdSequence; + } + + public void setGeneratedIdSequence(String generatedIdSequence) { + this.generatedIdSequence = generatedIdSequence; + } + + /** + * @see DatabaseField#foreign() + */ + public boolean isForeign() { + return foreign; + } + + public void setForeign(boolean foreign) { + this.foreign = foreign; + } + + /** + * For a foreign class which does not use the {@link DatabaseField} annotations, you need to inject the table + * configuration. + */ + public DatabaseTableConfig getForeignTableConfig() { + return foreignTableConfig; + } + + public void setForeignTableConfig(DatabaseTableConfig foreignTableConfig) { + this.foreignTableConfig = foreignTableConfig; + } + + /** + * @see DatabaseField#useGetSet() + */ + public boolean isUseGetSet() { + return useGetSet; + } + + public void setUseGetSet(boolean useGetSet) { + this.useGetSet = useGetSet; + } + + public Enum getUnknownEnumValue() { + return unknownEnumValue; + } + + public void setUnknownEnumValue(Enum unknownEnumValue) { + this.unknownEnumValue = unknownEnumValue; + } + + public boolean isThrowIfNull() { + return throwIfNull; + } + + public void setThrowIfNull(boolean throwIfNull) { + this.throwIfNull = throwIfNull; + } + + public boolean isPersisted() { + return persisted; + } + + public void setPersisted(boolean persisted) { + this.persisted = persisted; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public boolean isUnique() { + return unique; + } + + public void setUnique(boolean unique) { + this.unique = unique; + } + + public boolean isUniqueCombo() { + return uniqueCombo; + } + + public void setUniqueCombo(boolean uniqueCombo) { + this.uniqueCombo = uniqueCombo; + } + + public boolean isIndex() { + return index; + } + + public void setIndex(boolean index) { + this.index = index; + } + + public String getIndexName(String tableName) { + if (index && indexName == null) { + indexName = findIndexName(tableName); + } + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + public boolean isUniqueIndex() { + return uniqueIndex; + } + + public void setUniqueIndex(boolean uniqueIndex) { + this.uniqueIndex = uniqueIndex; + } + + public String getUniqueIndexName(String tableName) { + if (uniqueIndex && uniqueIndexName == null) { + uniqueIndexName = findIndexName(tableName); + } + return uniqueIndexName; + } + + public void setUniqueIndexName(String uniqueIndexName) { + this.uniqueIndexName = uniqueIndexName; + } + + public void setForeignAutoRefresh(boolean foreignAutoRefresh) { + this.foreignAutoRefresh = foreignAutoRefresh; + } + + public boolean isForeignAutoRefresh() { + return foreignAutoRefresh; + } + + public int getMaxForeignAutoRefreshLevel() { + /* + * We need to do this because otherwise things that inject the default max-foreign-auto-refresh value (config + * files, Android annotation hacks, etc) might turn on the auto-refresh by accident. + */ + if (foreignAutoRefresh) { + return maxForeignAutoRefreshLevel; + } else { + return NO_MAX_FOREIGN_AUTO_REFRESH_LEVEL_SPECIFIED; + } + } + + public void setMaxForeignAutoRefreshLevel(int maxForeignLevel) { + this.maxForeignAutoRefreshLevel = maxForeignLevel; + } + + /* + * Foreign collection field configurations + */ + + public boolean isForeignCollection() { + return foreignCollection; + } + + public void setForeignCollection(boolean foreignCollection) { + this.foreignCollection = foreignCollection; + } + + public boolean isForeignCollectionEager() { + return foreignCollectionEager; + } + + public void setForeignCollectionEager(boolean foreignCollectionEager) { + this.foreignCollectionEager = foreignCollectionEager; + } + + public int getForeignCollectionMaxEagerLevel() { + return foreignCollectionMaxEagerLevel; + } + + public void setForeignCollectionMaxEagerLevel(int foreignCollectionMaxEagerLevel) { + this.foreignCollectionMaxEagerLevel = foreignCollectionMaxEagerLevel; + } + + /** + * @deprecated Should use {@link #setForeignCollectionMaxEagerLevel(int)} + */ + @Deprecated + public void setMaxEagerForeignCollectionLevel(int maxEagerForeignCollectionLevel) { + this.foreignCollectionMaxEagerLevel = maxEagerForeignCollectionLevel; + } + + /** + * @deprecated Should use {@link #setForeignCollectionMaxEagerLevel(int)} + */ + @Deprecated + public void setForeignCollectionMaxEagerForeignCollectionLevel(int maxEagerForeignCollectionLevel) { + this.foreignCollectionMaxEagerLevel = maxEagerForeignCollectionLevel; + } + + public String getForeignCollectionColumnName() { + return foreignCollectionColumnName; + } + + public void setForeignCollectionColumnName(String foreignCollectionColumn) { + this.foreignCollectionColumnName = foreignCollectionColumn; + } + + public String getForeignCollectionOrderColumnName() { + return foreignCollectionOrderColumnName; + } + + /** + * @deprecated You should use {@link #setForeignCollectionOrderColumnName(String)} + */ + @Deprecated + public void setForeignCollectionOrderColumn(String foreignCollectionOrderColumn) { + this.foreignCollectionOrderColumnName = foreignCollectionOrderColumn; + } + + public void setForeignCollectionOrderColumnName(String foreignCollectionOrderColumn) { + this.foreignCollectionOrderColumnName = foreignCollectionOrderColumn; + } + + public boolean isForeignCollectionOrderAscending() { + return foreignCollectionOrderAscending; + } + + public void setForeignCollectionOrderAscending(boolean foreignCollectionOrderAscending) { + this.foreignCollectionOrderAscending = foreignCollectionOrderAscending; + } + + public String getForeignCollectionForeignFieldName() { + return foreignCollectionForeignFieldName; + } + + /** + * @deprecated You should use {@link #setForeignCollectionForeignFieldName(String)} + */ + @Deprecated + public void setForeignCollectionForeignColumnName(String foreignCollectionForeignColumnName) { + this.foreignCollectionForeignFieldName = foreignCollectionForeignColumnName; + } + + public void setForeignCollectionForeignFieldName(String foreignCollectionForeignFieldName) { + this.foreignCollectionForeignFieldName = foreignCollectionForeignFieldName; + } + + public Class getPersisterClass() { + return persisterClass; + } + + public void setPersisterClass(Class persisterClass) { + this.persisterClass = persisterClass; + } + + public boolean isAllowGeneratedIdInsert() { + return allowGeneratedIdInsert; + } + + public void setAllowGeneratedIdInsert(boolean allowGeneratedIdInsert) { + this.allowGeneratedIdInsert = allowGeneratedIdInsert; + } + + public String getColumnDefinition() { + return columnDefinition; + } + + public void setColumnDefinition(String columnDefinition) { + this.columnDefinition = columnDefinition; + } + + public boolean isForeignAutoCreate() { + return foreignAutoCreate; + } + + public void setForeignAutoCreate(boolean foreignAutoCreate) { + this.foreignAutoCreate = foreignAutoCreate; + } + + public boolean isVersion() { + return version; + } + + public void setVersion(boolean version) { + this.version = version; + } + + public String getForeignColumnName() { + return foreignColumnName; + } + + public void setForeignColumnName(String foreignColumnName) { + this.foreignColumnName = foreignColumnName; + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + /** + * Create and return a config converted from a {@link Field} that may have one of the following annotations: + * {@link DatabaseField}, {@link ForeignCollectionField}, or javax.persistence... + */ + public static DatabaseFieldConfig fromField(DatabaseType databaseType, String tableName, Field field) + throws SQLException { + + // first we lookup the @DatabaseField annotation + DatabaseField databaseField = field.getAnnotation(DatabaseField.class); + if (databaseField != null) { + if (databaseField.persisted()) { + return fromDatabaseField(databaseType, tableName, field, databaseField); + } else { + return null; + } + } + + // lastly we check for @ForeignCollectionField + ForeignCollectionField foreignCollection = field.getAnnotation(ForeignCollectionField.class); + if (foreignCollection != null) { + return fromForeignCollection(databaseType, field, foreignCollection); + } + + /* + * NOTE: to remove javax.persistence usage, comment the following lines out + */ + if (javaxPersistenceConfigurer == null) { + return null; + } else { + // this can be null + return javaxPersistenceConfigurer.createFieldConfig(databaseType, field); + } + } + + /** + * Find and return the appropriate getter method for field. + * + * @return Get method or null (or throws IllegalArgumentException) if none found. + */ + public static Method findGetMethod(Field field, boolean throwExceptions) throws IllegalArgumentException { + Method fieldGetMethod; + if (Locale.ENGLISH.equals(Locale.getDefault())) { + fieldGetMethod = + findMethodFromNames(field, true, throwExceptions, methodFromField(field, "get", null), + methodFromField(field, "is", null)); + } else { + fieldGetMethod = + findMethodFromNames(field, true, throwExceptions, methodFromField(field, "get", null), + methodFromField(field, "get", Locale.ENGLISH), methodFromField(field, "is", null), + methodFromField(field, "is", Locale.ENGLISH)); + } + if (fieldGetMethod == null) { + return null; + } + if (fieldGetMethod.getReturnType() != field.getType()) { + if (throwExceptions) { + throw new IllegalArgumentException("Return type of get method " + fieldGetMethod.getName() + + " does not return " + field.getType()); + } else { + return null; + } + } + return fieldGetMethod; + } + + /** + * Find and return the appropriate setter method for field. + * + * @return Set method or null (or throws IllegalArgumentException) if none found. + */ + public static Method findSetMethod(Field field, boolean throwExceptions) throws IllegalArgumentException { + Method fieldSetMethod; + if (Locale.ENGLISH.equals(Locale.getDefault())) { + fieldSetMethod = findMethodFromNames(field, false, throwExceptions, methodFromField(field, "set", null)); + } else { + fieldSetMethod = + findMethodFromNames(field, false, throwExceptions, methodFromField(field, "set", null), + methodFromField(field, "set", Locale.ENGLISH)); + } + if (fieldSetMethod == null) { + return null; + } + if (fieldSetMethod.getReturnType() != void.class) { + if (throwExceptions) { + throw new IllegalArgumentException("Return type of set method " + fieldSetMethod.getName() + + " returns " + fieldSetMethod.getReturnType() + " instead of void"); + } else { + return null; + } + } + return fieldSetMethod; + } + + public static DatabaseFieldConfig fromDatabaseField(DatabaseType databaseType, String tableName, Field field, + DatabaseField databaseField) { + DatabaseFieldConfig config = new DatabaseFieldConfig(); + config.fieldName = field.getName(); + if (databaseType.isEntityNamesMustBeUpCase()) { + config.fieldName = config.fieldName.toUpperCase(); + } + config.columnName = valueIfNotBlank(databaseField.columnName()); + config.dataType = databaseField.dataType(); + // NOTE: == did not work with the NO_DEFAULT string + String defaultValue = databaseField.defaultValue(); + if (!defaultValue.equals(DatabaseField.DEFAULT_STRING)) { + config.defaultValue = defaultValue; + } + config.width = databaseField.width(); + config.canBeNull = databaseField.canBeNull(); + config.id = databaseField.id(); + config.generatedId = databaseField.generatedId(); + config.generatedIdSequence = valueIfNotBlank(databaseField.generatedIdSequence()); + config.foreign = databaseField.foreign(); + config.useGetSet = databaseField.useGetSet(); + config.unknownEnumValue = findMatchingEnumVal(field, databaseField.unknownEnumName()); + config.throwIfNull = databaseField.throwIfNull(); + config.format = valueIfNotBlank(databaseField.format()); + config.unique = databaseField.unique(); + config.uniqueCombo = databaseField.uniqueCombo(); + + // add in the index information + config.index = databaseField.index(); + config.indexName = valueIfNotBlank(databaseField.indexName()); + config.uniqueIndex = databaseField.uniqueIndex(); + config.uniqueIndexName = valueIfNotBlank(databaseField.uniqueIndexName()); + config.foreignAutoRefresh = databaseField.foreignAutoRefresh(); + if (config.foreignAutoRefresh + || databaseField.maxForeignAutoRefreshLevel() != DatabaseField.DEFAULT_MAX_FOREIGN_AUTO_REFRESH_LEVEL) { + config.maxForeignAutoRefreshLevel = databaseField.maxForeignAutoRefreshLevel(); + } else { + config.maxForeignAutoRefreshLevel = NO_MAX_FOREIGN_AUTO_REFRESH_LEVEL_SPECIFIED; + } + config.persisterClass = databaseField.persisterClass(); + config.allowGeneratedIdInsert = databaseField.allowGeneratedIdInsert(); + config.columnDefinition = valueIfNotBlank(databaseField.columnDefinition()); + config.foreignAutoCreate = databaseField.foreignAutoCreate(); + config.version = databaseField.version(); + config.foreignColumnName = valueIfNotBlank(databaseField.foreignColumnName()); + config.readOnly = databaseField.readOnly(); + + return config; + } + + /** + * Process the settings when we are going to consume them. + */ + public void postProcess() { + if (foreignColumnName != null) { + foreignAutoRefresh = true; + } + if (foreignAutoRefresh && maxForeignAutoRefreshLevel == NO_MAX_FOREIGN_AUTO_REFRESH_LEVEL_SPECIFIED) { + maxForeignAutoRefreshLevel = DatabaseField.DEFAULT_MAX_FOREIGN_AUTO_REFRESH_LEVEL; + } + } + + /** + * Internal method that finds the matching enum for a configured field that has the name argument. + * + * @return The matching enum value or null if blank enum name. + * @throws IllegalArgumentException + * If the enum name is not known. + */ + public static Enum findMatchingEnumVal(Field field, String unknownEnumName) { + if (unknownEnumName == null || unknownEnumName.length() == 0) { + return null; + } + for (Enum enumVal : (Enum[]) field.getType().getEnumConstants()) { + if (enumVal.name().equals(unknownEnumName)) { + return enumVal; + } + } + throw new IllegalArgumentException("Unknwown enum unknown name " + unknownEnumName + " for field " + field); + } + + private static DatabaseFieldConfig fromForeignCollection(DatabaseType databaseType, Field field, + ForeignCollectionField foreignCollection) { + DatabaseFieldConfig config = new DatabaseFieldConfig(); + config.fieldName = field.getName(); + if (foreignCollection.columnName().length() > 0) { + config.columnName = foreignCollection.columnName(); + } + config.foreignCollection = true; + config.foreignCollectionEager = foreignCollection.eager(); + @SuppressWarnings("deprecation") + int maxEagerLevel = foreignCollection.maxEagerForeignCollectionLevel(); + if (maxEagerLevel != ForeignCollectionField.DEFAULT_MAX_EAGER_LEVEL) { + config.foreignCollectionMaxEagerLevel = maxEagerLevel; + } else { + config.foreignCollectionMaxEagerLevel = foreignCollection.maxEagerLevel(); + } + config.foreignCollectionOrderColumnName = valueIfNotBlank(foreignCollection.orderColumnName()); + config.foreignCollectionOrderAscending = foreignCollection.orderAscending(); + config.foreignCollectionColumnName = valueIfNotBlank(foreignCollection.columnName()); + String foreignFieldName = valueIfNotBlank(foreignCollection.foreignFieldName()); + if (foreignFieldName == null) { + @SuppressWarnings("deprecation") + String foreignColumnName = valueIfNotBlank(foreignCollection.foreignColumnName()); + config.foreignCollectionForeignFieldName = valueIfNotBlank(foreignColumnName); + } else { + config.foreignCollectionForeignFieldName = foreignFieldName; + } + return config; + } + + private String findIndexName(String tableName) { + if (columnName == null) { + return tableName + "_" + fieldName + "_idx"; + } else { + return tableName + "_" + columnName + "_idx"; + } + } + + private static String valueIfNotBlank(String newValue) { + if (newValue == null || newValue.length() == 0) { + return null; + } else { + return newValue; + } + } + + private static Method findMethodFromNames(Field field, boolean isGetMethod, boolean throwExceptions, + String... methodNames) { + NoSuchMethodException firstException = null; + for (String methodName : methodNames) { + try { + if (isGetMethod) { + // get method has no argument + return field.getDeclaringClass().getMethod(methodName); + } else { + // set method has same argument type as field + return field.getDeclaringClass().getMethod(methodName, field.getType()); + } + } catch (NoSuchMethodException nsme) { + if (firstException == null) { + firstException = nsme; + } + } + } + if (throwExceptions) { + throw new IllegalArgumentException("Could not find appropriate " + (isGetMethod ? "get" : "set") + + " method for " + field, firstException); + } else { + return null; + } + } + + private static String methodFromField(Field field, String prefix, Locale locale) { + String name = field.getName(); + String start = name.substring(0, 1); + if (locale == null) { + start = start.toUpperCase(); + } else { + start = start.toUpperCase(locale); + } + StringBuilder sb = new StringBuilder(); + sb.append(prefix); + sb.append(start); + sb.append(name, 1, name.length()); + return sb.toString(); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfigLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfigLoader.java new file mode 100644 index 0000000..8415f62 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/DatabaseFieldConfigLoader.java @@ -0,0 +1,423 @@ +package com.j256.ormlite.field; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.sql.SQLException; + +import com.j256.ormlite.misc.SqlExceptionUtil; + +/** + * Database field configuration loader which reads and writes {@link DatabaseFieldConfig} from a text file/stream. + * + * @author graywatson + */ +public class DatabaseFieldConfigLoader { + + private static final String CONFIG_FILE_START_MARKER = "# --field-start--"; + private static final String CONFIG_FILE_END_MARKER = "# --field-end--"; + + private static final int DEFAULT_MAX_EAGER_FOREIGN_COLLECTION_LEVEL = + ForeignCollectionField.DEFAULT_MAX_EAGER_LEVEL; + private static final DataPersister DEFAULT_DATA_PERSISTER = + DatabaseFieldConfig.DEFAULT_DATA_TYPE.getDataPersister(); + + /** + * Load a configuration in from a text file. + * + * @return A config if any of the fields were set otherwise null on EOF. + */ + public static DatabaseFieldConfig fromReader(BufferedReader reader) throws SQLException { + DatabaseFieldConfig config = new DatabaseFieldConfig(); + boolean anything = false; + while (true) { + String line; + try { + line = reader.readLine(); + } catch (IOException e) { + throw SqlExceptionUtil.create("Could not read DatabaseFieldConfig from stream", e); + } + if (line == null) { + break; + } + // we do this so we can support multiple class configs per file + if (line.equals(CONFIG_FILE_END_MARKER)) { + break; + } + // skip empty lines or comments + if (line.length() == 0 || line.startsWith("#") || line.equals(CONFIG_FILE_START_MARKER)) { + continue; + } + String[] parts = line.split("=", -2); + if (parts.length != 2) { + throw new SQLException("DatabaseFieldConfig reading from stream cannot parse line: " + line); + } + readField(config, parts[0], parts[1]); + anything = true; + } + // if we got any config lines then we return the config + if (anything) { + return config; + } else { + // otherwise we return null for none + return null; + } + } + + /** + * Write the configuration to a buffered writer. + */ + public static void write(BufferedWriter writer, DatabaseFieldConfig config, String tableName) throws SQLException { + try { + writeConfig(writer, config, tableName); + } catch (IOException e) { + throw SqlExceptionUtil.create("Could not write config to writer", e); + } + } + + private static final String FIELD_NAME_FIELD_NAME = "fieldName"; + private static final String FIELD_NAME_COLUMN_NAME = "columnName"; + private static final String FIELD_NAME_DATA_PERSISTER = "dataPersister"; + private static final String FIELD_NAME_DEFAULT_VALUE = "defaultValue"; + private static final String FIELD_NAME_WIDTH = "width"; + private static final String FIELD_NAME_CAN_BE_NULL = "canBeNull"; + private static final String FIELD_NAME_ID = "id"; + private static final String FIELD_NAME_GENERATED_ID = "generatedId"; + private static final String FIELD_NAME_GENERATED_ID_SEQUENCE = "generatedIdSequence"; + private static final String FIELD_NAME_FOREIGN = "foreign"; + private static final String FIELD_NAME_USE_GET_SET = "useGetSet"; + private static final String FIELD_NAME_UNKNOWN_ENUM_VALUE = "unknownEnumValue"; + private static final String FIELD_NAME_THROW_IF_NULL = "throwIfNull"; + private static final String FIELD_NAME_FORMAT = "format"; + private static final String FIELD_NAME_UNIQUE = "unique"; + private static final String FIELD_NAME_UNIQUE_COMBO = "uniqueCombo"; + private static final String FIELD_NAME_INDEX = "index"; + private static final String FIELD_NAME_INDEX_NAME = "indexName"; + private static final String FIELD_NAME_UNIQUE_INDEX = "uniqueIndex"; + private static final String FIELD_NAME_UNIQUE_INDEX_NAME = "uniqueIndexName"; + private static final String FIELD_NAME_FOREIGN_AUTO_REFRESH = "foreignAutoRefresh"; + private static final String FIELD_NAME_MAX_FOREIGN_AUTO_REFRESH_LEVEL = "maxForeignAutoRefreshLevel"; + private static final String FIELD_NAME_PERSISTER_CLASS = "persisterClass"; + private static final String FIELD_NAME_ALLOW_GENERATED_ID_INSERT = "allowGeneratedIdInsert"; + private static final String FIELD_NAME_COLUMN_DEFINITION = "columnDefinition"; + private static final String FIELD_NAME_FOREIGN_AUTO_CREATE = "foreignAutoCreate"; + private static final String FIELD_NAME_VERSION = "version"; + private static final String FIELD_NAME_FOREIGN_COLUMN_NAME = "foreignColumnName"; + private static final String FIELD_NAME_READ_ONLY = "readOnly"; + + private static final String FIELD_NAME_FOREIGN_COLLECTION = "foreignCollection"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_EAGER = "foreignCollectionEager"; + private static final String FIELD_NAME_MAX_EAGER_FOREIGN_COLLECTION_LEVEL_OLD = "maxEagerForeignCollectionLevel"; + private static final String FIELD_NAME_MAX_EAGER_FOREIGN_COLLECTION_LEVEL = "foreignCollectionMaxEagerLevel"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_COLUMN_NAME = "foreignCollectionColumnName"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_ORDER_COLUMN_NAME_OLD = "foreignCollectionOrderColumn"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_ORDER_COLUMN_NAME = "foreignCollectionOrderColumnName"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_ORDER_ASCENDING = "foreignCollectionOrderAscending"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_FOREIGN_FIELD_NAME_OLD = + "foreignCollectionForeignColumnName"; + private static final String FIELD_NAME_FOREIGN_COLLECTION_FOREIGN_FIELD_NAME = "foreignCollectionForeignFieldName"; + + /** + * Print the config to the writer. + */ + public static void writeConfig(BufferedWriter writer, DatabaseFieldConfig config, String tableName) + throws IOException { + writer.append(CONFIG_FILE_START_MARKER); + writer.newLine(); + if (config.getFieldName() != null) { + writer.append(FIELD_NAME_FIELD_NAME).append('=').append(config.getFieldName()); + writer.newLine(); + } + if (config.getColumnName() != null) { + writer.append(FIELD_NAME_COLUMN_NAME).append('=').append(config.getColumnName()); + writer.newLine(); + } + if (config.getDataPersister() != DEFAULT_DATA_PERSISTER) { + boolean found = false; + for (DataType dataType : DataType.values()) { + if (dataType.getDataPersister() == config.getDataPersister()) { + writer.append(FIELD_NAME_DATA_PERSISTER).append('=').append(dataType.name()); + writer.newLine(); + found = true; + break; + } + } + if (!found) { + throw new IllegalArgumentException("Unknown data persister field: " + config.getDataPersister()); + } + } + if (config.getDefaultValue() != null) { + writer.append(FIELD_NAME_DEFAULT_VALUE).append('=').append(config.getDefaultValue()); + writer.newLine(); + } + if (config.getWidth() != 0) { + writer.append(FIELD_NAME_WIDTH).append('=').append(Integer.toString(config.getWidth())); + writer.newLine(); + } + if (config.isCanBeNull() != DatabaseFieldConfig.DEFAULT_CAN_BE_NULL) { + writer.append(FIELD_NAME_CAN_BE_NULL).append('=').append(Boolean.toString(config.isCanBeNull())); + writer.newLine(); + } + if (config.isId()) { + writer.append(FIELD_NAME_ID).append('=').append("true"); + writer.newLine(); + } + if (config.isGeneratedId()) { + writer.append(FIELD_NAME_GENERATED_ID).append('=').append("true"); + writer.newLine(); + } + if (config.getGeneratedIdSequence() != null) { + writer.append(FIELD_NAME_GENERATED_ID_SEQUENCE).append('=').append(config.getGeneratedIdSequence()); + writer.newLine(); + } + if (config.isForeign()) { + writer.append(FIELD_NAME_FOREIGN).append('=').append("true"); + writer.newLine(); + } + if (config.isUseGetSet()) { + writer.append(FIELD_NAME_USE_GET_SET).append('=').append("true"); + writer.newLine(); + } + if (config.getUnknownEnumValue() != null) { + writer.append(FIELD_NAME_UNKNOWN_ENUM_VALUE) + .append('=') + .append(config.getUnknownEnumValue().getClass().getName()) + .append("#") + .append(config.getUnknownEnumValue().name()); + writer.newLine(); + } + if (config.isThrowIfNull()) { + writer.append(FIELD_NAME_THROW_IF_NULL).append('=').append("true"); + writer.newLine(); + } + if (config.getFormat() != null) { + writer.append(FIELD_NAME_FORMAT).append('=').append(config.getFormat()); + writer.newLine(); + } + if (config.isUnique()) { + writer.append(FIELD_NAME_UNIQUE).append('=').append("true"); + writer.newLine(); + } + if (config.isUniqueCombo()) { + writer.append(FIELD_NAME_UNIQUE_COMBO).append('=').append("true"); + writer.newLine(); + } + String indexName = config.getIndexName(tableName); + if (indexName != null) { + writer.append(FIELD_NAME_INDEX_NAME).append('=').append(indexName); + writer.newLine(); + } + String uniqueIndexName = config.getUniqueIndexName(tableName); + if (uniqueIndexName != null) { + writer.append(FIELD_NAME_UNIQUE_INDEX_NAME).append('=').append(uniqueIndexName); + writer.newLine(); + } + if (config.isForeignAutoRefresh()) { + writer.append(FIELD_NAME_FOREIGN_AUTO_REFRESH).append('=').append("true"); + writer.newLine(); + } + if (config.getMaxForeignAutoRefreshLevel() != DatabaseFieldConfig.NO_MAX_FOREIGN_AUTO_REFRESH_LEVEL_SPECIFIED) { + writer.append(FIELD_NAME_MAX_FOREIGN_AUTO_REFRESH_LEVEL) + .append('=') + .append(Integer.toString(config.getMaxForeignAutoRefreshLevel())); + writer.newLine(); + } + if (config.getPersisterClass() != DatabaseFieldConfig.DEFAULT_PERSISTER_CLASS) { + writer.append(FIELD_NAME_PERSISTER_CLASS).append('=').append(config.getPersisterClass().getName()); + writer.newLine(); + } + if (config.isAllowGeneratedIdInsert()) { + writer.append(FIELD_NAME_ALLOW_GENERATED_ID_INSERT).append('=').append("true"); + writer.newLine(); + } + if (config.getColumnDefinition() != null) { + writer.append(FIELD_NAME_COLUMN_DEFINITION).append('=').append(config.getColumnDefinition()); + writer.newLine(); + } + if (config.isForeignAutoCreate()) { + writer.append(FIELD_NAME_FOREIGN_AUTO_CREATE).append('=').append("true"); + writer.newLine(); + } + if (config.isVersion()) { + writer.append(FIELD_NAME_VERSION).append('=').append("true"); + writer.newLine(); + } + String foreignColumnName = config.getForeignColumnName(); + if (foreignColumnName != null) { + writer.append(FIELD_NAME_FOREIGN_COLUMN_NAME).append('=').append(foreignColumnName); + writer.newLine(); + } + if (config.isReadOnly()) { + writer.append(FIELD_NAME_READ_ONLY).append('=').append("true"); + writer.newLine(); + } + + /* + * Foreign collection settings: + */ + if (config.isForeignCollection()) { + writer.append(FIELD_NAME_FOREIGN_COLLECTION).append('=').append("true"); + writer.newLine(); + } + if (config.isForeignCollectionEager()) { + writer.append(FIELD_NAME_FOREIGN_COLLECTION_EAGER).append('=').append("true"); + writer.newLine(); + } + if (config.getForeignCollectionMaxEagerLevel() != DEFAULT_MAX_EAGER_FOREIGN_COLLECTION_LEVEL) { + writer.append(FIELD_NAME_MAX_EAGER_FOREIGN_COLLECTION_LEVEL) + .append('=') + .append(Integer.toString(config.getForeignCollectionMaxEagerLevel())); + writer.newLine(); + } + if (config.getForeignCollectionColumnName() != null) { + writer.append(FIELD_NAME_FOREIGN_COLLECTION_COLUMN_NAME) + .append('=') + .append(config.getForeignCollectionColumnName()); + writer.newLine(); + } + if (config.getForeignCollectionOrderColumnName() != null) { + writer.append(FIELD_NAME_FOREIGN_COLLECTION_ORDER_COLUMN_NAME) + .append('=') + .append(config.getForeignCollectionOrderColumnName()); + writer.newLine(); + } + if (config.isForeignCollectionOrderAscending() != DatabaseFieldConfig.DEFAULT_FOREIGN_COLLECTION_ORDER_ASCENDING) { + writer.append(FIELD_NAME_FOREIGN_COLLECTION_ORDER_ASCENDING) + .append('=') + .append(Boolean.toString(config.isForeignCollectionOrderAscending())); + writer.newLine(); + } + if (config.getForeignCollectionForeignFieldName() != null) { + writer.append(FIELD_NAME_FOREIGN_COLLECTION_FOREIGN_FIELD_NAME) + .append('=') + .append(config.getForeignCollectionForeignFieldName()); + writer.newLine(); + } + + writer.append(CONFIG_FILE_END_MARKER); + writer.newLine(); + } + + /** + * Set the configuration information for this field=value line. + */ + private static void readField(DatabaseFieldConfig config, String field, String value) { + if (field.equals(FIELD_NAME_FIELD_NAME)) { + config.setFieldName(value); + } else if (field.equals(FIELD_NAME_COLUMN_NAME)) { + config.setColumnName(value); + } else if (field.equals(FIELD_NAME_DATA_PERSISTER)) { + config.setDataPersister(DataType.valueOf(value).getDataPersister()); + } else if (field.equals(FIELD_NAME_DEFAULT_VALUE)) { + config.setDefaultValue(value); + } else if (field.equals(FIELD_NAME_WIDTH)) { + config.setWidth(Integer.parseInt(value)); + } else if (field.equals(FIELD_NAME_CAN_BE_NULL)) { + config.setCanBeNull(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_ID)) { + config.setId(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_GENERATED_ID)) { + config.setGeneratedId(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_GENERATED_ID_SEQUENCE)) { + config.setGeneratedIdSequence(value); + } else if (field.equals(FIELD_NAME_FOREIGN)) { + config.setForeign(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_USE_GET_SET)) { + config.setUseGetSet(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_UNKNOWN_ENUM_VALUE)) { + String[] parts = value.split("#", -2); + if (parts.length != 2) { + throw new IllegalArgumentException( + "Invalid value for unknownEnumValue which should be in class#name format: " + value); + } + Class enumClass; + try { + enumClass = Class.forName(parts[0]); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Unknown class specified for unknownEnumValue: " + value); + } + Object[] consts = enumClass.getEnumConstants(); + if (consts == null) { + throw new IllegalArgumentException("Invalid class is not an Enum for unknownEnumValue: " + value); + } + @SuppressWarnings("rawtypes") + Enum[] enumConstants = (Enum[]) consts; + boolean found = false; + for (Enum enumInstance : enumConstants) { + if (enumInstance.name().equals(parts[1])) { + config.setUnknownEnumValue(enumInstance); + found = true; + } + } + if (!found) { + throw new IllegalArgumentException("Invalid enum value name for unknownEnumvalue: " + value); + } + } else if (field.equals(FIELD_NAME_THROW_IF_NULL)) { + config.setThrowIfNull(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_FORMAT)) { + config.setFormat(value); + } else if (field.equals(FIELD_NAME_UNIQUE)) { + config.setUnique(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_UNIQUE_COMBO)) { + config.setUniqueCombo(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_INDEX)) { + config.setIndex(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_INDEX_NAME)) { + config.setIndex(true); + config.setIndexName(value); + } else if (field.equals(FIELD_NAME_UNIQUE_INDEX)) { + config.setUniqueIndex(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_UNIQUE_INDEX_NAME)) { + config.setUniqueIndex(true); + config.setUniqueIndexName(value); + } else if (field.equals(FIELD_NAME_FOREIGN_AUTO_REFRESH)) { + config.setForeignAutoRefresh(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_MAX_FOREIGN_AUTO_REFRESH_LEVEL)) { + config.setMaxForeignAutoRefreshLevel(Integer.parseInt(value)); + } else if (field.equals(FIELD_NAME_PERSISTER_CLASS)) { + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(value); + config.setPersisterClass(clazz); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Could not find persisterClass: " + value); + } + } else if (field.equals(FIELD_NAME_ALLOW_GENERATED_ID_INSERT)) { + config.setAllowGeneratedIdInsert(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_COLUMN_DEFINITION)) { + config.setColumnDefinition(value); + } else if (field.equals(FIELD_NAME_FOREIGN_AUTO_CREATE)) { + config.setForeignAutoCreate(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_VERSION)) { + config.setVersion(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_FOREIGN_COLUMN_NAME)) { + config.setForeignColumnName(value); + } else if (field.equals(FIELD_NAME_READ_ONLY)) { + config.setReadOnly(Boolean.parseBoolean(value)); + } + /** + * foreign collection field information + */ + else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION)) { + config.setForeignCollection(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_EAGER)) { + config.setForeignCollectionEager(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_MAX_EAGER_FOREIGN_COLLECTION_LEVEL_OLD)) { + config.setForeignCollectionMaxEagerLevel(Integer.parseInt(value)); + } else if (field.equals(FIELD_NAME_MAX_EAGER_FOREIGN_COLLECTION_LEVEL)) { + config.setForeignCollectionMaxEagerLevel(Integer.parseInt(value)); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_COLUMN_NAME)) { + config.setForeignCollectionColumnName(value); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_ORDER_COLUMN_NAME_OLD)) { + config.setForeignCollectionOrderColumnName(value); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_ORDER_COLUMN_NAME)) { + config.setForeignCollectionOrderColumnName(value); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_ORDER_ASCENDING)) { + config.setForeignCollectionOrderAscending(Boolean.parseBoolean(value)); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_FOREIGN_FIELD_NAME_OLD)) { + config.setForeignCollectionForeignFieldName(value); + } else if (field.equals(FIELD_NAME_FOREIGN_COLLECTION_FOREIGN_FIELD_NAME)) { + config.setForeignCollectionForeignFieldName(value); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/FieldConverter.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/FieldConverter.java new file mode 100644 index 0000000..0705e3b --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/FieldConverter.java @@ -0,0 +1,73 @@ +package com.j256.ormlite.field; + +import java.sql.SQLException; + +import com.j256.ormlite.db.BaseDatabaseType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Convert a Java object into the appropriate argument to a SQL statement and then back from the result set to the Java + * object. This allows databases to configure per-type conversion. This is used by the + * {@link BaseDatabaseType#getFieldConverter(DataPersister, FieldType)} method to find the converter for a particular + * database type. Databases can then override the default data conversion mechanisms as necessary. + * + * @author graywatson + */ +public interface FieldConverter { + + /** + * Convert a default string object and return the appropriate argument to a SQL insert or update statement. + */ + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException; + + /** + * Convert a Java object and return the appropriate argument to a SQL insert or update statement. + */ + public Object javaToSqlArg(FieldType fieldType, Object obj) throws SQLException; + + /** + * Return the SQL argument object extracted from the results associated with column in position columnPos. For + * example, if the type is a date-long then this will return a long value or null. + * + * @throws SQLException + * If there is a problem accessing the results data. + * @param fieldType + * Associated FieldType which may be null. + */ + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException; + + /** + * This is usually just a call that takes the result from {@link #resultToSqlArg(FieldType, DatabaseResults, int)} + * and passes it through {@link #sqlArgToJava(FieldType, Object, int)}. + */ + public Object resultToJava(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException; + + /** + * Return the object converted from the SQL arg to java. This takes the database representation and converts it into + * a Java object. For example, if the type is a date-long then this will take a long which is stored in the database + * and return a Date. + * + * @param fieldType + * Associated FieldType which may be null. + * @param sqlArg + * SQL argument converted with {@link #resultToSqlArg(FieldType, DatabaseResults, int)} which will not be + * null. + */ + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException; + + /** + * Return the SQL type that is stored in the database for this argument. + */ + public SqlType getSqlType(); + + /** + * Return whether or not this is a SQL "stream" object. Cannot get certain stream objects from the SQL results more + * than once. If true, the converter has to protect itself against null values. + */ + public boolean isStreamType(); + + /** + * Convert a string result value to the related Java field. + */ + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/FieldType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/FieldType.java new file mode 100644 index 0000000..5cf007a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/FieldType.java @@ -0,0 +1,1121 @@ +package com.j256.ormlite.field; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Map; + +import com.j256.ormlite.dao.BaseDaoImpl; +import com.j256.ormlite.dao.BaseForeignCollection; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.dao.EagerForeignCollection; +import com.j256.ormlite.dao.ForeignCollection; +import com.j256.ormlite.dao.LazyForeignCollection; +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.types.VoidType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.stmt.mapped.MappedQueryForId; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.DatabaseTableConfig; +import com.j256.ormlite.table.TableInfo; + +/** + * Per field information configured from the {@link DatabaseField} annotation and the associated {@link Field} in the + * class. Use the {@link #createFieldType} static method to instantiate the class. + * + * @author graywatson + */ +public class FieldType { + + /** default suffix added to fields that are id fields of foreign objects */ + public static final String FOREIGN_ID_FIELD_SUFFIX = "_id"; + + /* + * Default values. + * + * NOTE: These don't get any values so the compiler assigns them to the default values for the type. Ahhhh. Smart. + */ + private static boolean DEFAULT_VALUE_BOOLEAN; + private static byte DEFAULT_VALUE_BYTE; + private static char DEFAULT_VALUE_CHAR; + private static short DEFAULT_VALUE_SHORT; + private static int DEFAULT_VALUE_INT; + private static long DEFAULT_VALUE_LONG; + private static float DEFAULT_VALUE_FLOAT; + private static double DEFAULT_VALUE_DOUBLE; + + private final ConnectionSource connectionSource; + private final String tableName; + private final Field field; + private final String columnName; + private final DatabaseFieldConfig fieldConfig; + private final boolean isId; + private final boolean isGeneratedId; + private final String generatedIdSequence; + private final Method fieldGetMethod; + private final Method fieldSetMethod; + private final Class parentClass; + + private DataPersister dataPersister; + private Object defaultValue; + private Object dataTypeConfigObj; + + private FieldConverter fieldConverter; + private FieldType foreignIdField; + private TableInfo foreignTableInfo; + private FieldType foreignFieldType; + private BaseDaoImpl foreignDao; + private MappedQueryForId mappedQueryForId; + + /** + * ThreadLocal counters to detect initialization loops. Notice that there is _not_ an initValue() method on purpose. + * We don't want to create these if we don't have to. + */ + private static final ThreadLocal threadLevelCounters = new ThreadLocal(); + + /** + * You should use {@link FieldType#createFieldType} to instantiate one of these field if you have a {@link Field}. + */ + public FieldType(ConnectionSource connectionSource, String tableName, Field field, DatabaseFieldConfig fieldConfig, + Class parentClass) throws SQLException { + this.connectionSource = connectionSource; + this.tableName = tableName; + DatabaseType databaseType = connectionSource.getDatabaseType(); + this.field = field; + this.parentClass = parentClass; + + // post process our config settings + fieldConfig.postProcess(); + + Class clazz = field.getType(); + DataPersister dataPersister; + if (fieldConfig.getDataPersister() == null) { + Class persisterClass = fieldConfig.getPersisterClass(); + if (persisterClass == null || persisterClass == VoidType.class) { + dataPersister = DataPersisterManager.lookupForField(field); + } else { + Method method; + try { + method = persisterClass.getDeclaredMethod("getSingleton"); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not find getSingleton static method on class " + + persisterClass, e); + } + Object result; + try { + result = method.invoke(null); + } catch (InvocationTargetException e) { + throw SqlExceptionUtil.create("Could not run getSingleton method on class " + persisterClass, + e.getTargetException()); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not run getSingleton method on class " + persisterClass, e); + } + if (result == null) { + throw new SQLException("Static getSingleton method should not return null on class " + + persisterClass); + } + try { + dataPersister = (DataPersister) result; + } catch (Exception e) { + throw SqlExceptionUtil.create( + "Could not cast result of static getSingleton method to DataPersister from class " + + persisterClass, e); + } + } + } else { + dataPersister = fieldConfig.getDataPersister(); + if (!dataPersister.isValidForField(field)) { + StringBuilder sb = new StringBuilder(); + sb.append("Field class ").append(clazz.getName()); + sb.append(" for field ").append(this); + sb.append(" is not valid for type ").append(dataPersister); + Class primaryClass = dataPersister.getPrimaryClass(); + if (primaryClass != null) { + sb.append(", maybe should be " + primaryClass); + } + throw new IllegalArgumentException(sb.toString()); + } + } + String foreignColumnName = fieldConfig.getForeignColumnName(); + String defaultFieldName = field.getName(); + if (fieldConfig.isForeign() || fieldConfig.isForeignAutoRefresh() || foreignColumnName != null) { + if (dataPersister != null && dataPersister.isPrimitive()) { + throw new IllegalArgumentException("Field " + this + " is a primitive class " + clazz + + " but marked as foreign"); + } + if (foreignColumnName == null) { + defaultFieldName = defaultFieldName + FOREIGN_ID_FIELD_SUFFIX; + } else { + defaultFieldName = defaultFieldName + "_" + foreignColumnName; + } + if (ForeignCollection.class.isAssignableFrom(clazz)) { + throw new SQLException("Field '" + field.getName() + "' in class " + clazz + "' should use the @" + + ForeignCollectionField.class.getSimpleName() + " annotation not foreign=true"); + } + } else if (fieldConfig.isForeignCollection()) { + if (clazz != Collection.class && !ForeignCollection.class.isAssignableFrom(clazz)) { + throw new SQLException("Field class for '" + field.getName() + "' must be of class " + + ForeignCollection.class.getSimpleName() + " or Collection."); + } + Type type = field.getGenericType(); + if (!(type instanceof ParameterizedType)) { + throw new SQLException("Field class for '" + field.getName() + "' must be a parameterized Collection."); + } + Type[] genericArguments = ((ParameterizedType) type).getActualTypeArguments(); + if (genericArguments.length == 0) { + // i doubt this will ever be reached + throw new SQLException("Field class for '" + field.getName() + + "' must be a parameterized Collection with at least 1 type."); + } + } else if (dataPersister == null && (!fieldConfig.isForeignCollection())) { + if (byte[].class.isAssignableFrom(clazz)) { + throw new SQLException("ORMLite does not know how to store " + clazz + " for field '" + field.getName() + + "'. byte[] fields must specify dataType=DataType.BYTE_ARRAY or SERIALIZABLE"); + } else if (Serializable.class.isAssignableFrom(clazz)) { + throw new SQLException("ORMLite does not know how to store " + clazz + " for field '" + field.getName() + + "'. Use another class, custom persister, or to serialize it use " + + "dataType=DataType.SERIALIZABLE"); + } else { + throw new IllegalArgumentException("ORMLite does not know how to store " + clazz + " for field " + + field.getName() + ". Use another class or a custom persister."); + } + } + if (fieldConfig.getColumnName() == null) { + this.columnName = defaultFieldName; + } else { + this.columnName = fieldConfig.getColumnName(); + } + this.fieldConfig = fieldConfig; + if (fieldConfig.isId()) { + if (fieldConfig.isGeneratedId() || fieldConfig.getGeneratedIdSequence() != null) { + throw new IllegalArgumentException("Must specify one of id, generatedId, and generatedIdSequence with " + + field.getName()); + } + this.isId = true; + this.isGeneratedId = false; + this.generatedIdSequence = null; + } else if (fieldConfig.isGeneratedId()) { + if (fieldConfig.getGeneratedIdSequence() != null) { + throw new IllegalArgumentException("Must specify one of id, generatedId, and generatedIdSequence with " + + field.getName()); + } + this.isId = true; + this.isGeneratedId = true; + if (databaseType.isIdSequenceNeeded()) { + this.generatedIdSequence = databaseType.generateIdSequenceName(tableName, this); + } else { + this.generatedIdSequence = null; + } + } else if (fieldConfig.getGeneratedIdSequence() != null) { + this.isId = true; + this.isGeneratedId = true; + String seqName = fieldConfig.getGeneratedIdSequence(); + if (databaseType.isEntityNamesMustBeUpCase()) { + seqName = seqName.toUpperCase(); + } + this.generatedIdSequence = seqName; + } else { + this.isId = false; + this.isGeneratedId = false; + this.generatedIdSequence = null; + } + if (this.isId && (fieldConfig.isForeign() || fieldConfig.isForeignAutoRefresh())) { + throw new IllegalArgumentException("Id field " + field.getName() + " cannot also be a foreign object"); + } + if (fieldConfig.isUseGetSet()) { + this.fieldGetMethod = DatabaseFieldConfig.findGetMethod(field, true); + this.fieldSetMethod = DatabaseFieldConfig.findSetMethod(field, true); + } else { + if (!field.isAccessible()) { + try { + this.field.setAccessible(true); + } catch (SecurityException e) { + throw new IllegalArgumentException("Could not open access to field " + field.getName() + + ". You may have to set useGetSet=true to fix."); + } + } + this.fieldGetMethod = null; + this.fieldSetMethod = null; + } + if (fieldConfig.isAllowGeneratedIdInsert() && !fieldConfig.isGeneratedId()) { + throw new IllegalArgumentException("Field " + field.getName() + + " must be a generated-id if allowGeneratedIdInsert = true"); + } + if (fieldConfig.isForeignAutoRefresh() && !fieldConfig.isForeign()) { + throw new IllegalArgumentException("Field " + field.getName() + + " must have foreign = true if foreignAutoRefresh = true"); + } + if (fieldConfig.isForeignAutoCreate() && !fieldConfig.isForeign()) { + throw new IllegalArgumentException("Field " + field.getName() + + " must have foreign = true if foreignAutoCreate = true"); + } + if (fieldConfig.getForeignColumnName() != null && !fieldConfig.isForeign()) { + throw new IllegalArgumentException("Field " + field.getName() + + " must have foreign = true if foreignColumnName is set"); + } + if (fieldConfig.isVersion() && (dataPersister == null || !dataPersister.isValidForVersion())) { + throw new IllegalArgumentException("Field " + field.getName() + + " is not a valid type to be a version field"); + } + assignDataType(databaseType, dataPersister); + } + + /** + * Because we go recursive in a lot of situations if we construct DAOs inside of the FieldType constructor, we have + * to do this 2nd pass initialization so we can better use the DAO caches. + * + * @see BaseDaoImpl#initialize() + */ + public void configDaoInformation(ConnectionSource connectionSource, Class parentClass) throws SQLException { + Class fieldClass = field.getType(); + DatabaseType databaseType = connectionSource.getDatabaseType(); + TableInfo foreignTableInfo; + final FieldType foreignIdField; + final FieldType foreignFieldType; + final BaseDaoImpl foreignDao; + final MappedQueryForId mappedQueryForId; + + String foreignColumnName = fieldConfig.getForeignColumnName(); + if (fieldConfig.isForeignAutoRefresh() || foreignColumnName != null) { + DatabaseTableConfig tableConfig = fieldConfig.getForeignTableConfig(); + if (tableConfig == null) { + // NOTE: the cast is necessary for maven + foreignDao = (BaseDaoImpl) DaoManager.createDao(connectionSource, fieldClass); + foreignTableInfo = foreignDao.getTableInfo(); + } else { + tableConfig.extractFieldTypes(connectionSource); + // NOTE: the cast is necessary for maven + foreignDao = (BaseDaoImpl) DaoManager.createDao(connectionSource, tableConfig); + foreignTableInfo = foreignDao.getTableInfo(); + } + if (foreignColumnName == null) { + foreignIdField = foreignTableInfo.getIdField(); + if (foreignIdField == null) { + throw new IllegalArgumentException("Foreign field " + fieldClass + " does not have id field"); + } + } else { + foreignIdField = foreignTableInfo.getFieldTypeByColumnName(foreignColumnName); + if (foreignIdField == null) { + throw new IllegalArgumentException("Foreign field " + fieldClass + " does not have field named '" + + foreignColumnName + "'"); + } + } + @SuppressWarnings("unchecked") + MappedQueryForId castMappedQueryForId = + (MappedQueryForId) MappedQueryForId.build(databaseType, foreignTableInfo, + foreignIdField); + mappedQueryForId = castMappedQueryForId; + foreignFieldType = null; + } else if (fieldConfig.isForeign()) { + if (this.dataPersister != null && this.dataPersister.isPrimitive()) { + throw new IllegalArgumentException("Field " + this + " is a primitive class " + fieldClass + + " but marked as foreign"); + } + DatabaseTableConfig tableConfig = fieldConfig.getForeignTableConfig(); + if (tableConfig != null) { + tableConfig.extractFieldTypes(connectionSource); + // NOTE: the cast is necessary for maven + foreignDao = (BaseDaoImpl) DaoManager.createDao(connectionSource, tableConfig); + } else { + /* + * Initially we were only doing this just for BaseDaoEnabled.class and isForeignAutoCreate(). But we + * need it also for foreign fields because the alternative was to use reflection. Chances are if it is + * foreign we're going to need the DAO in the future anyway so we might as well create it. This also + * allows us to make use of any table configs. + */ + // NOTE: the cast is necessary for maven + foreignDao = (BaseDaoImpl) DaoManager.createDao(connectionSource, fieldClass); + } + foreignTableInfo = foreignDao.getTableInfo(); + foreignIdField = foreignTableInfo.getIdField(); + if (foreignIdField == null) { + throw new IllegalArgumentException("Foreign field " + fieldClass + " does not have id field"); + } + if (isForeignAutoCreate() && !foreignIdField.isGeneratedId()) { + throw new IllegalArgumentException("Field " + field.getName() + + ", if foreignAutoCreate = true then class " + fieldClass.getSimpleName() + + " must have id field with generatedId = true"); + } + foreignFieldType = null; + mappedQueryForId = null; + } else if (fieldConfig.isForeignCollection()) { + if (fieldClass != Collection.class && !ForeignCollection.class.isAssignableFrom(fieldClass)) { + throw new SQLException("Field class for '" + field.getName() + "' must be of class " + + ForeignCollection.class.getSimpleName() + " or Collection."); + } + Type type = field.getGenericType(); + if (!(type instanceof ParameterizedType)) { + throw new SQLException("Field class for '" + field.getName() + "' must be a parameterized Collection."); + } + Type[] genericArguments = ((ParameterizedType) type).getActualTypeArguments(); + if (genericArguments.length == 0) { + // i doubt this will ever be reached + throw new SQLException("Field class for '" + field.getName() + + "' must be a parameterized Collection with at least 1 type."); + } + + // If argument is a type variable we need to get arguments from superclass + if (genericArguments[0] instanceof TypeVariable) { + genericArguments = ((ParameterizedType) parentClass.getGenericSuperclass()).getActualTypeArguments(); + } + + if (!(genericArguments[0] instanceof Class)) { + throw new SQLException("Field class for '" + field.getName() + + "' must be a parameterized Collection whose generic argument is an entity class not: " + + genericArguments[0]); + } + Class collectionClazz = (Class) genericArguments[0]; + DatabaseTableConfig tableConfig = fieldConfig.getForeignTableConfig(); + BaseDaoImpl foundDao; + if (tableConfig == null) { + @SuppressWarnings("unchecked") + BaseDaoImpl castDao = + (BaseDaoImpl) DaoManager.createDao(connectionSource, collectionClazz); + foundDao = castDao; + } else { + @SuppressWarnings("unchecked") + BaseDaoImpl castDao = + (BaseDaoImpl) DaoManager.createDao(connectionSource, tableConfig); + foundDao = castDao; + } + foreignDao = foundDao; + foreignFieldType = findForeignFieldType(collectionClazz, parentClass, (BaseDaoImpl) foundDao); + foreignIdField = null; + foreignTableInfo = null; + mappedQueryForId = null; + } else { + foreignTableInfo = null; + foreignIdField = null; + foreignFieldType = null; + foreignDao = null; + mappedQueryForId = null; + } + + this.mappedQueryForId = mappedQueryForId; + this.foreignTableInfo = foreignTableInfo; + this.foreignFieldType = foreignFieldType; + this.foreignDao = foreignDao; + this.foreignIdField = foreignIdField; + + // we have to do this because if we habe a foreign field then our id type might have gone to an _id primitive + if (this.foreignIdField != null) { + assignDataType(databaseType, this.foreignIdField.getDataPersister()); + } + } + + public Field getField() { + return field; + } + + public String getTableName() { + return tableName; + } + + public String getFieldName() { + return field.getName(); + } + + /** + * Return the class of the field associated with this field type. + */ + public Class getType() { + return field.getType(); + } + + public String getColumnName() { + return columnName; + } + + public DataPersister getDataPersister() { + return dataPersister; + } + + public Object getDataTypeConfigObj() { + return dataTypeConfigObj; + } + + public SqlType getSqlType() { + return fieldConverter.getSqlType(); + } + + /** + * Return the default value as parsed from the {@link DatabaseFieldConfig#getDefaultValue()}. + */ + public Object getDefaultValue() { + return defaultValue; + } + + public int getWidth() { + return fieldConfig.getWidth(); + } + + public boolean isCanBeNull() { + return fieldConfig.isCanBeNull(); + } + + /** + * Return whether the field is an id field. It is an id if {@link DatabaseField#id}, + * {@link DatabaseField#generatedId}, OR {@link DatabaseField#generatedIdSequence} are enabled. + */ + public boolean isId() { + return isId; + } + + /** + * Return whether the field is a generated-id field. This is true if {@link DatabaseField#generatedId} OR + * {@link DatabaseField#generatedIdSequence} are enabled. + */ + public boolean isGeneratedId() { + return isGeneratedId; + } + + /** + * Return whether the field is a generated-id-sequence field. This is true if + * {@link DatabaseField#generatedIdSequence} is specified OR if {@link DatabaseField#generatedId} is enabled and the + * {@link DatabaseType#isIdSequenceNeeded} is enabled. If the latter is true then the sequence name will be + * auto-generated. + */ + public boolean isGeneratedIdSequence() { + return generatedIdSequence != null; + } + + /** + * Return the generated-id-sequence associated with the field or null if {@link #isGeneratedIdSequence} is false. + */ + public String getGeneratedIdSequence() { + return generatedIdSequence; + } + + public boolean isForeign() { + return fieldConfig.isForeign(); + } + + /** + * Assign to the data object the val corresponding to the fieldType. + */ + public void assignField(Object data, Object val, boolean parentObject, ObjectCache objectCache) throws SQLException { + // if this is a foreign object then val is the foreign object's id val + if (foreignIdField != null && val != null) { + // get the current field value which is the foreign-id + Object foreignId = extractJavaFieldValue(data); + /* + * See if we don't need to create a new foreign object. If we are refreshing and the id field has not + * changed then there is no need to create a new foreign object and maybe lose previously refreshed field + * information. + */ + if (foreignId != null && foreignId.equals(val)) { + return; + } + // awhitlock: raised as OrmLite issue: bug #122 + Object cachedVal; + ObjectCache foreignCache = foreignDao.getObjectCache(); + if (foreignCache == null) { + cachedVal = null; + } else { + cachedVal = foreignCache.get(getType(), val); + } + if (cachedVal != null) { + val = cachedVal; + } else if (!parentObject) { + // the value we are to assign to our field is now the foreign object itself + val = createForeignObject(val, objectCache); + } + } + + if (fieldSetMethod == null) { + try { + field.set(data, val); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Could not assign object '" + val + "' of type " + val.getClass() + + " to field " + this, e); + } catch (IllegalAccessException e) { + throw SqlExceptionUtil.create("Could not assign object '" + val + "' of type " + val.getClass() + + "' to field " + this, e); + } + } else { + try { + fieldSetMethod.invoke(data, val); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not call " + fieldSetMethod + " on object with '" + val + "' for " + + this, e); + } + } + } + + /** + * Assign an ID value to this field. + */ + public Object assignIdValue(Object data, Number val, ObjectCache objectCache) throws SQLException { + Object idVal = dataPersister.convertIdNumber(val); + if (idVal == null) { + throw new SQLException("Invalid class " + dataPersister + " for sequence-id " + this); + } else { + assignField(data, idVal, false, objectCache); + return idVal; + } + } + + /** + * Return the value from the field in the object that is defined by this FieldType. + */ + public FV extractRawJavaFieldValue(Object object) throws SQLException { + Object val; + if (fieldGetMethod == null) { + try { + // field object may not be a T yet + val = field.get(object); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not get field value for " + this, e); + } + } else { + try { + val = fieldGetMethod.invoke(object); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not call " + fieldGetMethod + " for " + this, e); + } + } + + @SuppressWarnings("unchecked") + FV converted = (FV) val; + return converted; + } + + /** + * Return the value from the field in the object that is defined by this FieldType. If the field is a foreign object + * then the ID of the field is returned instead. + */ + public Object extractJavaFieldValue(Object object) throws SQLException { + + Object val = extractRawJavaFieldValue(object); + + // if this is a foreign object then we want its id field + if (foreignIdField != null && val != null) { + val = foreignIdField.extractRawJavaFieldValue(val); + } + + return val; + } + + /** + * Extract a field from an object and convert to something suitable to be passed to SQL as an argument. + */ + public Object extractJavaFieldToSqlArgValue(Object object) throws SQLException { + return convertJavaFieldToSqlArgValue(extractJavaFieldValue(object)); + } + + /** + * Convert a field value to something suitable to be stored in the database. + */ + public Object convertJavaFieldToSqlArgValue(Object fieldVal) throws SQLException { + if (fieldVal == null) { + return null; + } else { + return fieldConverter.javaToSqlArg(this, fieldVal); + } + } + + /** + * Convert a string value into the appropriate Java field value. + */ + public Object convertStringToJavaField(String value, int columnPos) throws SQLException { + if (value == null) { + return null; + } else { + return fieldConverter.resultStringToJava(this, value, columnPos); + } + } + + /** + * Move the SQL value to the next one for version processing. + */ + public Object moveToNextValue(Object val) throws SQLException { + if (dataPersister == null) { + return null; + } else { + return dataPersister.moveToNextValue(val); + } + } + + /** + * Return the id field associated with the foreign object or null if none. + */ + public FieldType getForeignIdField() { + return foreignIdField; + } + + /** + * Call through to {@link DataPersister#isEscapedValue()} + */ + public boolean isEscapedValue() { + return dataPersister.isEscapedValue(); + } + + public Enum getUnknownEnumVal() { + return fieldConfig.getUnknownEnumValue(); + } + + /** + * Return the format of the field. + */ + public String getFormat() { + return fieldConfig.getFormat(); + } + + public boolean isUnique() { + return fieldConfig.isUnique(); + } + + public boolean isUniqueCombo() { + return fieldConfig.isUniqueCombo(); + } + + public String getIndexName() { + return fieldConfig.getIndexName(tableName); + } + + public String getUniqueIndexName() { + return fieldConfig.getUniqueIndexName(tableName); + } + + /** + * Call through to {@link DataPersister#isEscapedDefaultValue()} + */ + public boolean isEscapedDefaultValue() { + return dataPersister.isEscapedDefaultValue(); + } + + /** + * Call through to {@link DataPersister#isComparable()} + */ + public boolean isComparable() throws SQLException { + if (fieldConfig.isForeignCollection()) { + return false; + } + /* + * We've seen dataPersister being null here in some strange cases. Why? It may because someone is searching on + * an improper field. Or maybe a table-config does not match the Java object? + */ + if (dataPersister == null) { + throw new SQLException("Internal error. Data-persister is not configured for field. " + + "Please post _full_ exception with associated data objects to mailing list: " + this); + } else { + return dataPersister.isComparable(); + } + } + + /** + * Call through to {@link DataPersister#isArgumentHolderRequired()} + */ + public boolean isArgumentHolderRequired() { + return dataPersister.isArgumentHolderRequired(); + } + + /** + * Call through to {@link DatabaseFieldConfig#isForeignCollection()} + */ + public boolean isForeignCollection() { + return fieldConfig.isForeignCollection(); + } + + /** + * Build and return a foreign collection based on the field settings that matches the id argument. This can return + * null in certain circumstances. + * + * @param parent + * The parent object that we will set on each item in the collection. + * @param id + * The id of the foreign object we will look for. This can be null if we are creating an empty + * collection. + */ + public BaseForeignCollection buildForeignCollection(Object parent, FID id) throws SQLException { + // this can happen if we have a foreign-auto-refresh scenario + if (foreignFieldType == null) { + return null; + } + @SuppressWarnings("unchecked") + Dao castDao = (Dao) foreignDao; + if (!fieldConfig.isForeignCollectionEager()) { + // we know this won't go recursive so no need for the counters + return new LazyForeignCollection(castDao, parent, id, foreignFieldType, + fieldConfig.getForeignCollectionOrderColumnName(), fieldConfig.isForeignCollectionOrderAscending()); + } + + // try not to create level counter objects unless we have to + LevelCounters levelCounters = threadLevelCounters.get(); + if (levelCounters == null) { + if (fieldConfig.getForeignCollectionMaxEagerLevel() == 0) { + // then return a lazy collection instead + return new LazyForeignCollection(castDao, parent, id, foreignFieldType, + fieldConfig.getForeignCollectionOrderColumnName(), + fieldConfig.isForeignCollectionOrderAscending()); + } + levelCounters = new LevelCounters(); + threadLevelCounters.set(levelCounters); + } + + if (levelCounters.foreignCollectionLevel == 0) { + levelCounters.foreignCollectionLevelMax = fieldConfig.getForeignCollectionMaxEagerLevel(); + } + // are we over our level limit? + if (levelCounters.foreignCollectionLevel >= levelCounters.foreignCollectionLevelMax) { + // then return a lazy collection instead + return new LazyForeignCollection(castDao, parent, id, foreignFieldType, + fieldConfig.getForeignCollectionOrderColumnName(), fieldConfig.isForeignCollectionOrderAscending()); + } + levelCounters.foreignCollectionLevel++; + try { + return new EagerForeignCollection(castDao, parent, id, foreignFieldType, + fieldConfig.getForeignCollectionOrderColumnName(), fieldConfig.isForeignCollectionOrderAscending()); + } finally { + levelCounters.foreignCollectionLevel--; + } + } + + /** + * Get the result object from the results. A call through to {@link FieldConverter#resultToJava}. + */ + public T resultToJava(DatabaseResults results, Map columnPositions) throws SQLException { + Integer dbColumnPos = columnPositions.get(columnName); + if (dbColumnPos == null) { + dbColumnPos = results.findColumn(columnName); + columnPositions.put(columnName, dbColumnPos); + } + @SuppressWarnings("unchecked") + T converted = (T) fieldConverter.resultToJava(this, results, dbColumnPos); + if (fieldConfig.isForeign()) { + /* + * Subtle problem here. If your foreign field is a primitive and the value was null then this would return 0 + * from getInt(). We have to specifically test to see if we have a foreign field so if it is null we return + * a null value to not create the sub-object. + */ + if (results.wasNull(dbColumnPos)) { + return null; + } + } else if (dataPersister.isPrimitive()) { + if (fieldConfig.isThrowIfNull() && results.wasNull(dbColumnPos)) { + throw new SQLException("Results value for primitive field '" + field.getName() + + "' was an invalid null value"); + } + } else if (!fieldConverter.isStreamType() && results.wasNull(dbColumnPos)) { + // we can't check if we have a null if this is a stream type + return null; + } + return converted; + } + + /** + * Call through to {@link DataPersister#isSelfGeneratedId()} + */ + public boolean isSelfGeneratedId() { + return dataPersister.isSelfGeneratedId(); + } + + /** + * Call through to {@link DatabaseFieldConfig#isAllowGeneratedIdInsert()} + */ + public boolean isAllowGeneratedIdInsert() { + return fieldConfig.isAllowGeneratedIdInsert(); + } + + /** + * Call through to {@link DatabaseFieldConfig#getColumnDefinition()} + */ + public String getColumnDefinition() { + return fieldConfig.getColumnDefinition(); + } + + /** + * Call through to {@link DatabaseFieldConfig#isForeignAutoCreate()} + */ + public boolean isForeignAutoCreate() { + return fieldConfig.isForeignAutoCreate(); + } + + /** + * Call through to {@link DatabaseFieldConfig#isVersion()} + */ + public boolean isVersion() { + return fieldConfig.isVersion(); + } + + /** + * Call through to {@link DataPersister#generateId()} + */ + public Object generateId() { + return dataPersister.generateId(); + } + + /** + * Call through to {@link DatabaseFieldConfig#isReadOnly()} + */ + public boolean isReadOnly() { + return fieldConfig.isReadOnly(); + } + + /** + * Return the value of field in the data argument if it is not the default value for the class. If it is the default + * then null is returned. + */ + public FV getFieldValueIfNotDefault(Object object) throws SQLException { + @SuppressWarnings("unchecked") + FV fieldValue = (FV) extractJavaFieldValue(object); + if (isFieldValueDefault(fieldValue)) { + return null; + } else { + return fieldValue; + } + } + + /** + * Return whether or not the data object has a default value passed for this field of this type. + */ + public boolean isObjectsFieldValueDefault(Object object) throws SQLException { + Object fieldValue = extractJavaFieldValue(object); + return isFieldValueDefault(fieldValue); + } + + /** + * Return whether or not the field value passed in is the default value for the type of the field. Null will return + * true. + */ + public Object getJavaDefaultValueDefault() { + if (field.getType() == boolean.class) { + return DEFAULT_VALUE_BOOLEAN; + } else if (field.getType() == byte.class || field.getType() == Byte.class) { + return DEFAULT_VALUE_BYTE; + } else if (field.getType() == char.class || field.getType() == Character.class) { + return DEFAULT_VALUE_CHAR; + } else if (field.getType() == short.class || field.getType() == Short.class) { + return DEFAULT_VALUE_SHORT; + } else if (field.getType() == int.class || field.getType() == Integer.class) { + return DEFAULT_VALUE_INT; + } else if (field.getType() == long.class || field.getType() == Long.class) { + return DEFAULT_VALUE_LONG; + } else if (field.getType() == float.class || field.getType() == Float.class) { + return DEFAULT_VALUE_FLOAT; + } else if (field.getType() == double.class || field.getType() == Double.class) { + return DEFAULT_VALUE_DOUBLE; + } else { + return null; + } + } + + /** + * Pass the foreign data argument to the foreign {@link Dao#create(Object)} method. + */ + public int createWithForeignDao(T foreignData) throws SQLException { + @SuppressWarnings("unchecked") + Dao castDao = (Dao) foreignDao; + return castDao.create(foreignData); + } + + /** + * Return An instantiated {@link FieldType} or null if the field does not have a {@link DatabaseField} annotation. + */ + public static FieldType createFieldType(ConnectionSource connectionSource, String tableName, Field field, + Class parentClass) throws SQLException { + DatabaseType databaseType = connectionSource.getDatabaseType(); + DatabaseFieldConfig fieldConfig = DatabaseFieldConfig.fromField(databaseType, tableName, field); + if (fieldConfig == null) { + return null; + } else { + return new FieldType(connectionSource, tableName, field, fieldConfig, parentClass); + } + } + + @Override + public boolean equals(Object arg) { + if (arg == null || arg.getClass() != this.getClass()) { + return false; + } + FieldType other = (FieldType) arg; + return field.equals(other.field) + && (parentClass == null ? other.parentClass == null : parentClass.equals(other.parentClass)); + } + + @Override + public int hashCode() { + return field.hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":name=" + field.getName() + ",class=" + + field.getDeclaringClass().getSimpleName(); + } + + private Object createForeignObject(Object val, ObjectCache objectCache) throws SQLException { + + // try to stop the level counters objects from being created + LevelCounters levelCounters = threadLevelCounters.get(); + if (levelCounters == null) { + // only create a shell if we are not in auto-refresh mode + if (!fieldConfig.isForeignAutoRefresh()) { + return createForeignShell(val, objectCache); + } + levelCounters = new LevelCounters(); + threadLevelCounters.set(levelCounters); + } + + // we record the current auto-refresh level which will be used along the way + if (levelCounters.autoRefreshLevel == 0) { + // if we aren't in an auto-refresh loop then don't _start_ an new loop if auto-refresh is not configured + if (!fieldConfig.isForeignAutoRefresh()) { + return createForeignShell(val, objectCache); + } + levelCounters.autoRefreshLevelMax = fieldConfig.getMaxForeignAutoRefreshLevel(); + } + // if we have recursed the proper number of times, return a shell with just the id set + if (levelCounters.autoRefreshLevel >= levelCounters.autoRefreshLevelMax) { + return createForeignShell(val, objectCache); + } + + /* + * We may not have a mapped query for id because we aren't auto-refreshing ourselves. But a parent class may be + * auto-refreshing us with a level > 1 so we may need to build our query-for-id optimization on the fly here. + */ + if (mappedQueryForId == null) { + @SuppressWarnings("unchecked") + MappedQueryForId castMappedQueryForId = + (MappedQueryForId) MappedQueryForId.build(connectionSource.getDatabaseType(), + ((BaseDaoImpl) foreignDao).getTableInfo(), foreignIdField); + mappedQueryForId = castMappedQueryForId; + } + levelCounters.autoRefreshLevel++; + try { + DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(); + try { + // recurse and get the sub-object + return mappedQueryForId.execute(databaseConnection, val, objectCache); + } finally { + connectionSource.releaseConnection(databaseConnection); + } + } finally { + levelCounters.autoRefreshLevel--; + if (levelCounters.autoRefreshLevel <= 0) { + threadLevelCounters.remove(); + } + } + } + + /** + * Create a shell object and assign its id field. + */ + private Object createForeignShell(Object val, ObjectCache objectCache) throws SQLException { + Object foreignObject = foreignTableInfo.createObject(); + foreignIdField.assignField(foreignObject, val, false, objectCache); + return foreignObject; + } + + /** + * Return whether or not the field value passed in is the default value for the type of the field. Null will return + * true. + */ + private boolean isFieldValueDefault(Object fieldValue) { + if (fieldValue == null) { + return true; + } else { + return fieldValue.equals(getJavaDefaultValueDefault()); + } + } + + /** + * If we have a class Foo with a collection of Bar's then we go through Bar's DAO looking for a Foo field. We need + * this field to build the query that is able to find all Bar's that have foo_id that matches our id. + */ + private FieldType findForeignFieldType(Class clazz, Class foreignClass, BaseDaoImpl foreignDao) + throws SQLException { + String foreignColumnName = fieldConfig.getForeignCollectionForeignFieldName(); + for (FieldType fieldType : foreignDao.getTableInfo().getFieldTypes()) { + if (fieldType.getType() == foreignClass + && (foreignColumnName == null || fieldType.getField().getName().equals(foreignColumnName))) { + if (!fieldType.fieldConfig.isForeign() && !fieldType.fieldConfig.isForeignAutoRefresh()) { + // this may never be reached + throw new SQLException("Foreign collection object " + clazz + " for field '" + field.getName() + + "' contains a field of class " + foreignClass + " but it's not foreign"); + } + return fieldType; + } + } + // build our complex error message + StringBuilder sb = new StringBuilder(); + sb.append("Foreign collection class ").append(clazz.getName()); + sb.append(" for field '").append(field.getName()).append("' column-name does not contain a foreign field"); + if (foreignColumnName != null) { + sb.append(" named '").append(foreignColumnName).append('\''); + } + sb.append(" of class ").append(foreignClass.getName()); + throw new SQLException(sb.toString()); + } + + /** + * Configure our data persister and any dependent fields. We have to do this here because both the constructor and + * {@link #configDaoInformation} method can set the data-type. + */ + private void assignDataType(DatabaseType databaseType, DataPersister dataPersister) throws SQLException { + dataPersister = databaseType.getDataPersister(dataPersister, this); + this.dataPersister = dataPersister; + if (dataPersister == null) { + if (!fieldConfig.isForeign() && !fieldConfig.isForeignCollection()) { + // may never happen but let's be careful out there + throw new SQLException("Data persister for field " + this + + " is null but the field is not a foreign or foreignCollection"); + } + return; + } + this.fieldConverter = databaseType.getFieldConverter(dataPersister, this); + if (this.isGeneratedId && !dataPersister.isValidGeneratedType()) { + StringBuilder sb = new StringBuilder(); + sb.append("Generated-id field '").append(field.getName()); + sb.append("' in ").append(field.getDeclaringClass().getSimpleName()); + sb.append(" can't be type ").append(dataPersister.getSqlType()); + sb.append(". Must be one of: "); + for (DataType dataType : DataType.values()) { + DataPersister persister = dataType.getDataPersister(); + if (persister != null && persister.isValidGeneratedType()) { + sb.append(dataType).append(' '); + } + } + throw new IllegalArgumentException(sb.toString()); + } + if (fieldConfig.isThrowIfNull() && !dataPersister.isPrimitive()) { + throw new SQLException("Field " + field.getName() + " must be a primitive if set with throwIfNull"); + } + if (this.isId && !dataPersister.isAppropriateId()) { + throw new SQLException("Field '" + field.getName() + "' is of data type " + dataPersister + + " which cannot be the ID field"); + } + this.dataTypeConfigObj = dataPersister.makeConfigObject(this); + String defaultStr = fieldConfig.getDefaultValue(); + if (defaultStr == null) { + this.defaultValue = null; + } else if (this.isGeneratedId) { + throw new SQLException("Field '" + field.getName() + "' cannot be a generatedId and have a default value '" + + defaultStr + "'"); + } else { + this.defaultValue = this.fieldConverter.parseDefaultString(this, defaultStr); + } + } + + private static class LevelCounters { + // current auto-refresh recursion level + int autoRefreshLevel; + // maximum auto-refresh recursion level + int autoRefreshLevelMax; + + // current foreign-collection recursion level + int foreignCollectionLevel; + // maximum foreign-collection recursion level + int foreignCollectionLevelMax; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/ForeignCollectionField.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/ForeignCollectionField.java new file mode 100644 index 0000000..37c082c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/ForeignCollectionField.java @@ -0,0 +1,110 @@ +package com.j256.ormlite.field; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.ForeignCollection; +import com.j256.ormlite.dao.LazyForeignCollection; +import com.j256.ormlite.stmt.QueryBuilder; + +/** + * Annotation that identifies a {@link ForeignCollection} field in a class that corresponds to objects in a foreign + * table that match the foreign-id of the current class. + * + *
+ * @ForeignCollection(id = true)
+ * private ForeignCollection<Order> orders;
+ * 
+ * + * @author graywatson + */ +@Target(FIELD) +@Retention(RUNTIME) +public @interface ForeignCollectionField { + + /** + * @see #maxEagerForeignCollectionLevel() + */ + public static final int DEFAULT_MAX_EAGER_LEVEL = 1; + + /** + *

+ * Set to true if the collection is a an eager collection where all of the results should be retrieved when the + * parent object is retrieved. Default is false (lazy) when the results will not be retrieved until you ask for the + * iterator from the collection. + *

+ * + *

+ * NOTE: If this is false (i.e. we have a lazy collection) then a connection is held open to the database as + * you iterate through the collection. This means that you need to make sure it is closed when you finish. See + * {@link LazyForeignCollection#iterator()} for more information. + *

+ * + *

+ * WARNING: By default, if you have eager collections of objects that themselves have eager collections, the + * inner collection will be created as lazy for performance reasons. If you need change this see the + * {@link #maxEagerLevel()} setting below. + *

+ */ + boolean eager() default false; + + /** + * @deprecated Should use {@link #maxEagerLevel()} + */ + @Deprecated + int maxEagerForeignCollectionLevel() default DEFAULT_MAX_EAGER_LEVEL; + + /** + * Set this to be the number of times to expand an eager foreign collection's foreign collection. If you query for A + * and it has an eager foreign-collection of field B which has an eager foreign-collection of field C ..., then a + * lot of database operations are going to happen whenever you query for A. By default this value is 1 meaning that + * if you query for A, the collection of B will be eager fetched but each of the B objects will have a lazy + * collection instead of an eager collection of C. It should be increased only if you know what you are doing. + */ + int maxEagerLevel() default DEFAULT_MAX_EAGER_LEVEL; + + /** + * The name of the column. This is only used when you want to match the string passed to + * {@link Dao#getEmptyForeignCollection(String)} or when you want to specify it in + * {@link QueryBuilder#selectColumns(String...)}. + */ + String columnName() default ""; + + /** + * The name of the column in the object that we should order by. + */ + String orderColumnName() default ""; + + /** + * If an order column has been defined with {@link #orderColumnName()}, this sets the order as ascending (true, the + * default) or descending (false). + */ + boolean orderAscending() default true; + + /** + * @deprecated This has been renamed as {@link #foreignFieldName()} to make it more consistent to how it works. + */ + @Deprecated + String foreignColumnName() default ""; + + /** + * Name of the _field_ (not the column name) in the class that the collection is holding that corresponds to the + * collection. This is needed if there are two foreign fields in the class in the collection (such as a tree + * structure) and you want to identify which column you want in this collection. + * + *

+ * WARNING: Due to some internal complexities, this it field/member name in the class and _not_ the + * column-name. + *

+ */ + String foreignFieldName() default ""; + + /* + * NOTE to developers: if you add fields here you have to add them to the DatabaseFieldConfigLoader, + * DatabaseFieldConfigLoaderTest, and DatabaseTableConfigUtil. + */ +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/SqlType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/SqlType.java new file mode 100644 index 0000000..9ddc079 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/SqlType.java @@ -0,0 +1,36 @@ +package com.j256.ormlite.field; + +/** + * The SQL data types that are supported. These are basically an enumeration of the constants in java.sql.Types. + * + *

+ * NOTE: If you add types here you will need to add to the various DatabaseType implementors' appendColumnArg() + * method. + *

+ * + * @author graywatson + */ +public enum SqlType { + + STRING, + LONG_STRING, + DATE, + BOOLEAN, + CHAR, + BYTE, + BYTE_ARRAY, + SHORT, + INTEGER, + LONG, + FLOAT, + DOUBLE, + SERIALIZABLE, + BLOB, + BIG_DECIMAL, + UUID, + // for other types handled by custom persisters + OTHER, + UNKNOWN, + // end + ; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDataType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDataType.java new file mode 100644 index 0000000..f7829ab --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDataType.java @@ -0,0 +1,180 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; +import java.sql.SQLException; + +import com.j256.ormlite.field.BaseFieldConverter; +import com.j256.ormlite.field.DataPersister; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Base data type that defines the default persistance methods for the various data types. + * + *

+ * Here's a good page about the mapping for a number of + * database types: + *

+ * + *

+ * NOTE: If you are creating your own custom database persister, you probably will need to override the + * {@link BaseFieldConverter#sqlArgToJava(FieldType, Object, int)} method as well which converts from a SQL data to + * java. + *

+ * + * @author graywatson + */ +public abstract class BaseDataType extends BaseFieldConverter implements DataPersister { + + private final static Class[] NO_CLASSES = new Class[0]; + + /** + * Type of the data as it is persisted in SQL-land. For example, if you are storing a DateTime, you might consider + * this to be a {@link SqlType#LONG} if you are storing it as epoche milliseconds. + */ + private final SqlType sqlType; + private final Class[] classes; + + /** + * @param sqlType + * Type of the class as it is persisted in the databases. + * @param classes + * Associated classes for this type. These should be specified if you want this type to be always used + * for these Java classes. If this is a custom persister then this array should be empty. + */ + public BaseDataType(SqlType sqlType, Class[] classes) { + this.sqlType = sqlType; + this.classes = classes; + } + + /** + * @param sqlType + * Type of the class as it is persisted in the databases. + */ + public BaseDataType(SqlType sqlType) { + this.sqlType = sqlType; + this.classes = NO_CLASSES; + } + + public abstract Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException; + + public abstract Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) + throws SQLException; + + public boolean isValidForField(Field field) { + if (classes.length == 0) { + // we can't figure out the types so we just say it is valid + return true; + } + for (Class clazz : classes) { + if (clazz.isAssignableFrom(field.getType())) { + return true; + } + } + // if classes are specified and one of them should match + return false; + } + + public Class getPrimaryClass() { + if (classes.length == 0) { + return null; + } else { + return classes[0]; + } + } + + /** + * @throws SQLException + * If there are problems creating the config object. Needed for subclasses. + */ + public Object makeConfigObject(FieldType fieldType) throws SQLException { + return null; + } + + public SqlType getSqlType() { + return sqlType; + } + + public Class[] getAssociatedClasses() { + return classes; + } + + public String[] getAssociatedClassNames() { + return null; + } + + public Object convertIdNumber(Number number) { + // by default the type cannot convert an id number + return null; + } + + public boolean isValidGeneratedType() { + return false; + } + + public boolean isEscapedDefaultValue() { + // default is to not escape the type if it is a number + return isEscapedValue(); + } + + public boolean isEscapedValue() { + return true; + } + + public boolean isPrimitive() { + return false; + } + + public boolean isComparable() { + return true; + } + + public boolean isAppropriateId() { + return true; + } + + public boolean isArgumentHolderRequired() { + return false; + } + + public boolean isSelfGeneratedId() { + return false; + } + + public Object generateId() { + throw new IllegalStateException("Should not have tried to generate this type"); + } + + public int getDefaultWidth() { + return 0; + } + + public boolean dataIsEqual(Object fieldObj1, Object fieldObj2) { + if (fieldObj1 == null) { + return (fieldObj2 == null); + } else if (fieldObj2 == null) { + return false; + } else { + return fieldObj1.equals(fieldObj2); + } + } + + public boolean isValidForVersion() { + return false; + } + + /** + * Move the current-value to the next value. Used for the version field. + * + * @throws SQLException + * For sub-classes. + */ + public Object moveToNextValue(Object currentValue) throws SQLException { + return null; + } + + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) throws SQLException { + return sqlArgToJava(fieldType, parseDefaultString(fieldType, stringValue), columnPos); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDateType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDateType.java new file mode 100644 index 0000000..eb14c0c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseDateType.java @@ -0,0 +1,96 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Base class for all of the {@link java.sql.Date} class types. + * + * @author graywatson + */ +public abstract class BaseDateType extends BaseDataType { + + protected static final DateStringFormatConfig defaultDateFormatConfig = new DateStringFormatConfig( + "yyyy-MM-dd HH:mm:ss.SSSSSS"); + + protected BaseDateType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + protected BaseDateType(SqlType sqlType) { + super(sqlType); + } + + protected static DateStringFormatConfig convertDateStringConfig(FieldType fieldType, + DateStringFormatConfig defaultDateFormatConfig) { + if (fieldType == null) { + return defaultDateFormatConfig; + } + DateStringFormatConfig configObj = (DateStringFormatConfig) fieldType.getDataTypeConfigObj(); + if (configObj == null) { + return defaultDateFormatConfig; + } else { + return (DateStringFormatConfig) configObj; + } + } + + protected static Date parseDateString(DateStringFormatConfig formatConfig, String dateStr) throws ParseException { + DateFormat dateFormat = formatConfig.getDateFormat(); + return dateFormat.parse(dateStr); + } + + protected static String normalizeDateString(DateStringFormatConfig formatConfig, String dateStr) + throws ParseException { + DateFormat dateFormat = formatConfig.getDateFormat(); + Date date = dateFormat.parse(dateStr); + return dateFormat.format(date); + } + + protected static class DateStringFormatConfig { + private final ThreadLocal threadLocal = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat(dateFormatStr); + } + }; + final String dateFormatStr; + public DateStringFormatConfig(String dateFormatStr) { + this.dateFormatStr = dateFormatStr; + } + public DateFormat getDateFormat() { + return threadLocal.get(); + } + @Override + public String toString() { + return dateFormatStr; + } + } + + @Override + public boolean isValidForVersion() { + return true; + } + + @Override + public Object moveToNextValue(Object currentValue) { + long newVal = System.currentTimeMillis(); + if (currentValue == null) { + return new Date(newVal); + } else if (newVal == ((Date) currentValue).getTime()) { + return new Date(newVal + 1L); + } else { + return new Date(newVal); + } + } + + @Override + public boolean isValidForField(Field field) { + return (field.getType() == Date.class); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseEnumType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseEnumType.java new file mode 100644 index 0000000..dd10ceb --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BaseEnumType.java @@ -0,0 +1,39 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Base class for the enum classes to provide a utility method. + * + * @author graywatson + */ +public abstract class BaseEnumType extends BaseDataType { + + protected BaseEnumType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + protected BaseEnumType(SqlType sqlType) { + super(sqlType); + } + + protected static Enum enumVal(FieldType fieldType, Object val, Enum enumVal, Enum unknownEnumVal) + throws SQLException { + if (enumVal != null) { + return enumVal; + } else if (unknownEnumVal == null) { + throw new SQLException("Cannot get enum value of '" + val + "' for field " + fieldType); + } else { + return unknownEnumVal; + } + } + + @Override + public boolean isValidForField(Field field) { + return field.getType().isEnum(); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalNumericType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalNumericType.java new file mode 100644 index 0000000..ccdd17c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalNumericType.java @@ -0,0 +1,66 @@ +package com.j256.ormlite.field.types; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a {@link BigInteger} object as a NUMERIC SQL database field. + * + * @author graywatson + */ +public class BigDecimalNumericType extends BaseDataType { + + private static final BigDecimalNumericType singleTon = new BigDecimalNumericType(); + + public static BigDecimalNumericType getSingleton() { + return singleTon; + } + + private BigDecimalNumericType() { + // this has no classes because {@link BigDecimalString} is the default + super(SqlType.BIG_DECIMAL); + } + + /** + * Here for others to subclass. + */ + protected BigDecimalNumericType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + try { + return new BigDecimal(defaultStr); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Problems with field " + fieldType + " parsing default BigDecimal string '" + + defaultStr + "'", e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getBigDecimal(columnPos); + } + + @Override + public boolean isAppropriateId() { + return false; + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public Class getPrimaryClass() { + return BigDecimal.class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalStringType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalStringType.java new file mode 100644 index 0000000..54ef4ce --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigDecimalStringType.java @@ -0,0 +1,78 @@ +package com.j256.ormlite.field.types; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a {@link BigInteger} object. + * + * @author graywatson + */ +public class BigDecimalStringType extends BaseDataType { + + public static int DEFAULT_WIDTH = 255; + + private static final BigDecimalStringType singleTon = new BigDecimalStringType(); + + public static BigDecimalStringType getSingleton() { + return singleTon; + } + + private BigDecimalStringType() { + super(SqlType.STRING, new Class[] { BigDecimal.class }); + } + + /** + * Here for others to subclass. + */ + protected BigDecimalStringType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + try { + return new BigDecimal(defaultStr).toString(); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Problems with field " + fieldType + " parsing default BigDecimal string '" + + defaultStr + "'", e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getString(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + try { + return new BigDecimal((String) sqlArg); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Problems with column " + columnPos + " parsing BigDecimal string '" + sqlArg + + "'", e); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + BigDecimal bigInteger = (BigDecimal) obj; + return bigInteger.toString(); + } + + @Override + public int getDefaultWidth() { + return DEFAULT_WIDTH; + } + + @Override + public boolean isAppropriateId() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigIntegerType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigIntegerType.java new file mode 100644 index 0000000..030b8fe --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BigIntegerType.java @@ -0,0 +1,77 @@ +package com.j256.ormlite.field.types; + +import java.math.BigInteger; +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a {@link BigInteger} object. + * + * @author graywatson + */ +public class BigIntegerType extends BaseDataType { + + public static int DEFAULT_WIDTH = 255; + + private static final BigIntegerType singleTon = new BigIntegerType(); + + public static BigIntegerType getSingleton() { + return singleTon; + } + + protected BigIntegerType() { + super(SqlType.STRING, new Class[] { BigInteger.class }); + } + + /** + * Here for others to subclass. + */ + protected BigIntegerType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + try { + return new BigInteger(defaultStr).toString(); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Problems with field " + fieldType + " parsing default BigInteger string '" + + defaultStr + "'", e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getString(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + try { + return new BigInteger((String) sqlArg); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Problems with column " + columnPos + " parsing BigInteger string '" + sqlArg + + "'", e); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + BigInteger bigInteger = (BigInteger) obj; + return bigInteger.toString(); + } + + @Override + public int getDefaultWidth() { + return DEFAULT_WIDTH; + } + + @Override + public boolean isAppropriateId() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanCharType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanCharType.java new file mode 100644 index 0000000..1e91e64 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanCharType.java @@ -0,0 +1,81 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Booleans can be stored in the database as the character '1' '0'. You can change the characters by specifying a format + * string. It must be a string with 2 characters. The first character is the value for TRUE, the second is FALSE. You + * must choose this DataType specifically with the {@link DatabaseField#dataType()} specifier. + * + *
+ * @DatabaseField(format = "YN", dataType = DataType.BOOLEAN_CHAR)
+ * 
+ * + * Thanks much to stew. + * + * @author graywatson + */ +public class BooleanCharType extends BooleanType { + + private static final String DEFAULT_TRUE_FALSE_FORMAT = "10"; + + private static final BooleanCharType singleTon = new BooleanCharType(); + + public static BooleanCharType getSingleton() { + return singleTon; + } + + public BooleanCharType() { + super(SqlType.STRING); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return javaToSqlArg(fieldType, Boolean.parseBoolean(defaultStr)); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + String format = (String) fieldType.getDataTypeConfigObj(); + return ((Boolean) obj ? format.charAt(0) : format.charAt(1)); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getChar(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + String format = (String) fieldType.getDataTypeConfigObj(); + return ((Character) sqlArg == format.charAt(0) ? Boolean.TRUE : Boolean.FALSE); + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) { + if (stringValue.length() == 0) { + return Boolean.FALSE; + } else { + return sqlArgToJava(fieldType, stringValue.charAt(0), columnPos); + } + } + + @Override + public Object makeConfigObject(FieldType fieldType) throws SQLException { + String format = fieldType.getFormat(); + if (format == null) { + return DEFAULT_TRUE_FALSE_FORMAT; + } else if (format.length() == 2 && format.charAt(0) != format.charAt(1)) { + return format; + } else { + throw new SQLException( + "Invalid boolean format must have 2 different characters that represent true/false like \"10\" or \"tf\": " + + format); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanIntegerType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanIntegerType.java new file mode 100644 index 0000000..48083c9 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanIntegerType.java @@ -0,0 +1,65 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Booleans can be stored in the database as the integer column type and the value 1 (really non-0) for true and 0 for + * false. You must choose this DataType specifically with the {@link DatabaseField#dataType()} specifier. + * + *
+ * @DatabaseField(dataType = DataType.BOOLEAN_INTEGER)
+ * 
+ * + * Thanks much to stew. + * + * @author graywatson + */ +public class BooleanIntegerType extends BooleanType { + + private static final Integer TRUE_VALUE = Integer.valueOf(1); + private static final Integer FALSE_VALUE = Integer.valueOf(0); + + private static final BooleanIntegerType singleTon = new BooleanIntegerType(); + + public static BooleanIntegerType getSingleton() { + return singleTon; + } + + public BooleanIntegerType() { + super(SqlType.INTEGER); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return javaToSqlArg(fieldType, Boolean.parseBoolean(defaultStr)); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + return ((Boolean) obj ? TRUE_VALUE : FALSE_VALUE); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getInt(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + return ((Integer) sqlArg == 0 ? Boolean.FALSE : Boolean.TRUE); + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) { + if (stringValue.length() == 0) { + return Boolean.FALSE; + } else { + return sqlArgToJava(fieldType, Integer.parseInt(stringValue), columnPos); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanObjectType.java new file mode 100644 index 0000000..3e9c174 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanObjectType.java @@ -0,0 +1,53 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Boolean object. + * + * @author graywatson + */ +public class BooleanObjectType extends BaseDataType { + + private static final BooleanObjectType singleTon = new BooleanObjectType(); + + public static BooleanObjectType getSingleton() { + return singleTon; + } + + private BooleanObjectType() { + super(SqlType.BOOLEAN, new Class[] { Boolean.class }); + } + + protected BooleanObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + protected BooleanObjectType(SqlType sqlType) { + super(sqlType); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Boolean.parseBoolean(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Boolean) results.getBoolean(columnPos); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public boolean isAppropriateId() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanType.java new file mode 100644 index 0000000..83aa49e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/BooleanType.java @@ -0,0 +1,34 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a boolean primitive. + * + * @author graywatson + */ +public class BooleanType extends BooleanObjectType { + + private static final BooleanType singleTon = new BooleanType(); + + public static BooleanType getSingleton() { + return singleTon; + } + + private BooleanType() { + super(SqlType.BOOLEAN, new Class[] { boolean.class }); + } + + protected BooleanType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + protected BooleanType(SqlType sqlType) { + super(sqlType); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteArrayType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteArrayType.java new file mode 100644 index 0000000..3400b37 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteArrayType.java @@ -0,0 +1,74 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.util.Arrays; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a byte[] object. + * + * @author graywatson + */ +public class ByteArrayType extends BaseDataType { + + private static final ByteArrayType singleTon = new ByteArrayType(); + + public static ByteArrayType getSingleton() { + return singleTon; + } + + private ByteArrayType() { + super(SqlType.BYTE_ARRAY); + } + + /** + * Here for others to subclass. + */ + protected ByteArrayType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + throw new SQLException("byte[] type cannot have default values"); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (byte[]) results.getBytes(columnPos); + } + + @Override + public boolean isAppropriateId() { + return false; + } + + @Override + public boolean isArgumentHolderRequired() { + return true; + } + + @Override + public boolean dataIsEqual(Object fieldObj1, Object fieldObj2) { + if (fieldObj1 == null) { + return (fieldObj2 == null); + } else if (fieldObj2 == null) { + return false; + } else { + return Arrays.equals((byte[]) fieldObj1, (byte[]) fieldObj2); + } + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) throws SQLException { + throw new SQLException("byte[] type cannot be converted from string to Java"); + } + + @Override + public Class getPrimaryClass() { + return byte[].class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteObjectType.java new file mode 100644 index 0000000..420c788 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteObjectType.java @@ -0,0 +1,44 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Byte object. + * + * @author graywatson + */ +public class ByteObjectType extends BaseDataType { + + private static final ByteObjectType singleTon = new ByteObjectType(); + + public static ByteObjectType getSingleton() { + return singleTon; + } + + private ByteObjectType() { + super(SqlType.BYTE, new Class[] { Byte.class }); + } + + protected ByteObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Byte.parseByte(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Byte) results.getByte(columnPos); + } + + @Override + public boolean isEscapedValue() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteType.java new file mode 100644 index 0000000..ee833ff --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ByteType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a byte primitive. + * + * @author graywatson + */ +public class ByteType extends ByteObjectType { + + private static final ByteType singleTon = new ByteType(); + + public static ByteType getSingleton() { + return singleTon; + } + + private ByteType() { + super(SqlType.BYTE, new Class[] { byte.class }); + } + + /** + * Here for others to subclass. + */ + protected ByteType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharType.java new file mode 100644 index 0000000..94c972d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharType.java @@ -0,0 +1,45 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a char primitive. + * + * @author graywatson + */ +public class CharType extends CharacterObjectType { + + private static final CharType singleTon = new CharType(); + + public static CharType getSingleton() { + return singleTon; + } + + private CharType() { + super(SqlType.CHAR, new Class[] { char.class }); + } + + /** + * Here for others to subclass. + */ + protected CharType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) { + Character character = (Character) javaObject; + // this is required because otherwise we try to store \0 in the database + if (character == null || character == 0) { + return null; + } else { + return character; + } + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharacterObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharacterObjectType.java new file mode 100644 index 0000000..3f4b338 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/CharacterObjectType.java @@ -0,0 +1,43 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Character object. + * + * @author graywatson + */ +public class CharacterObjectType extends BaseDataType { + + private static final CharacterObjectType singleTon = new CharacterObjectType(); + + public static CharacterObjectType getSingleton() { + return singleTon; + } + + private CharacterObjectType() { + super(SqlType.CHAR, new Class[] { Character.class }); + } + + protected CharacterObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + if (defaultStr.length() != 1) { + throw new SQLException("Problems with field " + fieldType + ", default string to long for Character: '" + + defaultStr + "'"); + } + return (Character) defaultStr.charAt(0); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Character) results.getChar(columnPos); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateLongType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateLongType.java new file mode 100644 index 0000000..2252a47 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateLongType.java @@ -0,0 +1,74 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.util.Date; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Persists the {@link java.util.Date} Java class as long milliseconds since epoch. + * + *

+ * NOTE: This is not the same as the {@link java.sql.Date} class. + *

+ * + * @author graywatson + */ +public class DateLongType extends BaseDateType { + + private static final DateLongType singleTon = new DateLongType(); + + public static DateLongType getSingleton() { + return singleTon; + } + + private DateLongType() { + super(SqlType.LONG); + } + + /** + * Here for others to subclass. + */ + protected DateLongType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + try { + return Long.parseLong(defaultStr); + } catch (NumberFormatException e) { + throw SqlExceptionUtil.create("Problems with field " + fieldType + " parsing default date-long value: " + + defaultStr, e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getLong(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + return new Date((Long) sqlArg); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + Date date = (Date) obj; + return (Long) date.getTime(); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public Class getPrimaryClass() { + return Date.class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateStringType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateStringType.java new file mode 100644 index 0000000..587e752 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateStringType.java @@ -0,0 +1,99 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Date; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a {@link java.util.Date} object as a String. + * + * @author graywatson + */ +public class DateStringType extends BaseDateType { + + public static int DEFAULT_WIDTH = 50; + + private static final DateStringType singleTon = new DateStringType(); + + public static DateStringType getSingleton() { + return singleTon; + } + + private DateStringType() { + super(SqlType.STRING); + } + + protected DateStringType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + protected DateStringType(SqlType sqlType) { + super(sqlType); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + DateStringFormatConfig formatConfig = convertDateStringConfig(fieldType, defaultDateFormatConfig); + try { + // we parse to make sure it works and then format it again + return normalizeDateString(formatConfig, defaultStr); + } catch (ParseException e) { + throw SqlExceptionUtil.create("Problems with field " + fieldType + " parsing default date-string '" + + defaultStr + "' using '" + formatConfig + "'", e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getString(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + String value = (String) sqlArg; + DateStringFormatConfig formatConfig = convertDateStringConfig(fieldType, defaultDateFormatConfig); + try { + return parseDateString(formatConfig, value); + } catch (ParseException e) { + throw SqlExceptionUtil.create("Problems with column " + columnPos + " parsing date-string '" + value + + "' using '" + formatConfig + "'", e); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + DateFormat dateFormat = convertDateStringConfig(fieldType, defaultDateFormatConfig).getDateFormat(); + return dateFormat.format((Date) obj); + } + + @Override + public Object makeConfigObject(FieldType fieldType) { + String format = fieldType.getFormat(); + if (format == null) { + return defaultDateFormatConfig; + } else { + return new DateStringFormatConfig(format); + } + } + + @Override + public int getDefaultWidth() { + return DEFAULT_WIDTH; + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) throws SQLException { + return sqlArgToJava(fieldType, stringValue, columnPos); + } + + @Override + public Class getPrimaryClass() { + return byte[].class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateTimeType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateTimeType.java new file mode 100644 index 0000000..5a80a31 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateTimeType.java @@ -0,0 +1,147 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.sql.SQLException; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * A custom persister that is able to store the org.joda.time.DateTime class in the database as epoch-millis long + * integer. This is done with reflection so we don't have to introduce the dependency. + * + *

+ * NOTE: Because this class uses reflection, you have to specify this using {@link DatabaseField#dataType()}. It + * won't be detected automatically. + *

+ * + * @author graywatson + */ +public class DateTimeType extends BaseDataType { + + private static final DateTimeType singleTon = new DateTimeType(); + private static Class dateTimeClass = null; + private static Method getMillisMethod = null; + private static Constructor millisConstructor = null; + private static final String[] associatedClassNames = new String[] { "org.joda.time.DateTime" }; + + private DateTimeType() { + super(SqlType.LONG); + } + + protected DateTimeType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + public static DateTimeType getSingleton() { + return singleTon; + } + + @Override + public String[] getAssociatedClassNames() { + return associatedClassNames; + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) throws SQLException { + return extractMillis(javaObject); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + try { + return Long.parseLong(defaultStr); + } catch (NumberFormatException e) { + throw SqlExceptionUtil.create("Problems with field " + fieldType + " parsing default DateTime value: " + + defaultStr, e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getLong(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + return createInstance((Long) sqlArg); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public boolean isAppropriateId() { + return false; + } + + @Override + public Class getPrimaryClass() { + try { + return getDateTimeClass(); + } catch (ClassNotFoundException e) { + // ignore the exception + return null; + } + } + + @Override + public boolean isValidForVersion() { + return true; + } + + @Override + public Object moveToNextValue(Object currentValue) throws SQLException { + long newVal = System.currentTimeMillis(); + if (currentValue == null) { + return createInstance(newVal); + } + Long currentVal = extractMillis(currentValue); + if (newVal == currentVal) { + return createInstance(newVal + 1L); + } else { + return createInstance(newVal); + } + } + + private Object createInstance(Long sqlArg) throws SQLException { + try { + if (millisConstructor == null) { + Class clazz = getDateTimeClass(); + millisConstructor = clazz.getConstructor(long.class); + } + return millisConstructor.newInstance(sqlArg); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not use reflection to construct a Joda DateTime", e); + } + } + + private Long extractMillis(Object javaObject) throws SQLException { + try { + if (getMillisMethod == null) { + Class clazz = getDateTimeClass(); + getMillisMethod = clazz.getMethod("getMillis"); + } + if (javaObject == null) { + return null; + } else { + return (Long) getMillisMethod.invoke(javaObject); + } + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not use reflection to get millis from Joda DateTime: " + javaObject, e); + } + } + + private Class getDateTimeClass() throws ClassNotFoundException { + if (dateTimeClass == null) { + dateTimeClass = Class.forName("org.joda.time.DateTime"); + } + return dateTimeClass; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateType.java new file mode 100644 index 0000000..ce1fe75 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DateType.java @@ -0,0 +1,76 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.sql.Timestamp; +import java.text.ParseException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a {@link java.util.Date} object. + * + *

+ * NOTE: This is not the same as the {@link java.sql.Date} class that is handled by {@link SqlDateType}. + *

+ * + * @author graywatson + */ +public class DateType extends BaseDateType { + + private static final DateType singleTon = new DateType(); + + public static DateType getSingleton() { + return singleTon; + } + + private DateType() { + super(SqlType.DATE, new Class[] { java.util.Date.class }); + } + + protected DateType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + DateStringFormatConfig dateFormatConfig = convertDateStringConfig(fieldType, getDefaultDateFormatConfig()); + try { + return new Timestamp(parseDateString(dateFormatConfig, defaultStr).getTime()); + } catch (ParseException e) { + throw SqlExceptionUtil.create("Problems parsing default date string '" + defaultStr + "' using '" + + dateFormatConfig + '\'', e); + } + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getTimestamp(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + Timestamp value = (Timestamp) sqlArg; + return new java.util.Date(value.getTime()); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) { + java.util.Date date = (java.util.Date) javaObject; + return new Timestamp(date.getTime()); + } + + @Override + public boolean isArgumentHolderRequired() { + return true; + } + + /** + * Return the default date format configuration. + */ + protected DateStringFormatConfig getDefaultDateFormatConfig() { + return defaultDateFormatConfig; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleObjectType.java new file mode 100644 index 0000000..5d8cf33 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleObjectType.java @@ -0,0 +1,44 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Double object. + * + * @author graywatson + */ +public class DoubleObjectType extends BaseDataType { + + private static final DoubleObjectType singleTon = new DoubleObjectType(); + + public static DoubleObjectType getSingleton() { + return singleTon; + } + + private DoubleObjectType() { + super(SqlType.DOUBLE, new Class[] { Double.class }); + } + + protected DoubleObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Double.parseDouble(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Double) results.getDouble(columnPos); + } + + @Override + public boolean isEscapedValue() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleType.java new file mode 100644 index 0000000..56e859c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/DoubleType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a double primitive. + * + * @author graywatson + */ +public class DoubleType extends DoubleObjectType { + + private static final DoubleType singleTon = new DoubleType(); + + public static DoubleType getSingleton() { + return singleTon; + } + + private DoubleType() { + super(SqlType.DOUBLE, new Class[] { double.class }); + } + + /** + * Here for others to subclass. + */ + protected DoubleType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumIntegerType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumIntegerType.java new file mode 100644 index 0000000..3799483 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumIntegerType.java @@ -0,0 +1,90 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Persists an Enum Java class as its ordinal integer value. You can also specify the {@link EnumStringType} as the + * type. + * + * @author graywatson + */ +public class EnumIntegerType extends BaseEnumType { + + private static final EnumIntegerType singleTon = new EnumIntegerType(); + + public static EnumIntegerType getSingleton() { + return singleTon; + } + + private EnumIntegerType() { + super(SqlType.INTEGER); + } + + /** + * Here for others to subclass. + */ + protected EnumIntegerType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Integer.parseInt(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getInt(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + if (fieldType == null) { + return sqlArg; + } + // do this once + Integer valInteger = (Integer) sqlArg; + @SuppressWarnings("unchecked") + Map> enumIntMap = (Map>) fieldType.getDataTypeConfigObj(); + if (enumIntMap == null) { + return enumVal(fieldType, valInteger, null, fieldType.getUnknownEnumVal()); + } else { + return enumVal(fieldType, valInteger, enumIntMap.get(valInteger), fieldType.getUnknownEnumVal()); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + Enum enumVal = (Enum) obj; + return (Integer) enumVal.ordinal(); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public Object makeConfigObject(FieldType fieldType) throws SQLException { + Map> enumIntMap = new HashMap>(); + Enum[] constants = (Enum[]) fieldType.getType().getEnumConstants(); + if (constants == null) { + throw new SQLException("Field " + fieldType + " improperly configured as type " + this); + } + for (Enum enumVal : constants) { + enumIntMap.put(enumVal.ordinal(), enumVal); + } + return enumIntMap; + } + + @Override + public Class getPrimaryClass() { + return int.class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumStringType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumStringType.java new file mode 100644 index 0000000..3eca3de --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/EnumStringType.java @@ -0,0 +1,85 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists an enum as its string value. You can also use the {@link EnumIntegerType}. + * + * @author graywatson + */ +public class EnumStringType extends BaseEnumType { + + public static int DEFAULT_WIDTH = 100; + + private static final EnumStringType singleTon = new EnumStringType(); + + public static EnumStringType getSingleton() { + return singleTon; + } + + private EnumStringType() { + super(SqlType.STRING, new Class[] { Enum.class }); + } + + /** + * Here for others to subclass. + */ + protected EnumStringType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getString(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + if (fieldType == null) { + return sqlArg; + } + String value = (String) sqlArg; + @SuppressWarnings("unchecked") + Map> enumStringMap = (Map>) fieldType.getDataTypeConfigObj(); + if (enumStringMap == null) { + return enumVal(fieldType, value, null, fieldType.getUnknownEnumVal()); + } else { + return enumVal(fieldType, value, enumStringMap.get(value), fieldType.getUnknownEnumVal()); + } + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return defaultStr; + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + Enum enumVal = (Enum) obj; + return enumVal.name(); + } + + @Override + public Object makeConfigObject(FieldType fieldType) throws SQLException { + Map> enumStringMap = new HashMap>(); + Enum[] constants = (Enum[]) fieldType.getType().getEnumConstants(); + if (constants == null) { + throw new SQLException("Field " + fieldType + " improperly configured as type " + this); + } + for (Enum enumVal : constants) { + enumStringMap.put(enumVal.name(), enumVal); + } + return enumStringMap; + } + + @Override + public int getDefaultWidth() { + return DEFAULT_WIDTH; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatObjectType.java new file mode 100644 index 0000000..7faea76 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatObjectType.java @@ -0,0 +1,44 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a boolean primitive. + * + * @author graywatson + */ +public class FloatObjectType extends BaseDataType { + + private static final FloatObjectType singleTon = new FloatObjectType(); + + public static FloatObjectType getSingleton() { + return singleTon; + } + + private FloatObjectType() { + super(SqlType.FLOAT, new Class[] { Float.class }); + } + + protected FloatObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Float) results.getFloat(columnPos); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Float.parseFloat(defaultStr); + } + + @Override + public boolean isEscapedValue() { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatType.java new file mode 100644 index 0000000..b08bd3b --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/FloatType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a float primitive. + * + * @author graywatson + */ +public class FloatType extends FloatObjectType { + + private static final FloatType singleTon = new FloatType(); + + public static FloatType getSingleton() { + return singleTon; + } + + private FloatType() { + super(SqlType.FLOAT, new Class[] { float.class }); + } + + /** + * Here for others to subclass. + */ + protected FloatType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntType.java new file mode 100644 index 0000000..ff14747 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a integer primitive. + * + * @author graywatson + */ +public class IntType extends IntegerObjectType { + + private static final IntType singleTon = new IntType(); + + public static IntType getSingleton() { + return singleTon; + } + + private IntType() { + super(SqlType.INTEGER, new Class[] { int.class }); + } + + /** + * Here for others to subclass. + */ + protected IntType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntegerObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntegerObjectType.java new file mode 100644 index 0000000..b3897f7 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/IntegerObjectType.java @@ -0,0 +1,68 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Integer object. + * + * @author graywatson + */ +public class IntegerObjectType extends BaseDataType { + + private static final IntegerObjectType singleTon = new IntegerObjectType(); + + public static IntegerObjectType getSingleton() { + return singleTon; + } + + private IntegerObjectType() { + super(SqlType.INTEGER, new Class[] { Integer.class }); + } + + protected IntegerObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Integer.parseInt(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Integer) results.getInt(columnPos); + } + + @Override + public Object convertIdNumber(Number number) { + return (Integer) number.intValue(); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public boolean isValidGeneratedType() { + return true; + } + + @Override + public boolean isValidForVersion() { + return true; + } + + @Override + public Object moveToNextValue(Object currentValue) { + if (currentValue == null) { + return (Integer) 1; + } else { + return ((Integer) currentValue) + 1; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongObjectType.java new file mode 100644 index 0000000..eff59cb --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongObjectType.java @@ -0,0 +1,68 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Long object. + * + * @author graywatson + */ +public class LongObjectType extends BaseDataType { + + private static final LongObjectType singleTon = new LongObjectType(); + + public static LongObjectType getSingleton() { + return singleTon; + } + + private LongObjectType() { + super(SqlType.LONG, new Class[] { Long.class }); + } + + protected LongObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Long.parseLong(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Long) results.getLong(columnPos); + } + + @Override + public Object convertIdNumber(Number number) { + return (Long) number.longValue(); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public boolean isValidGeneratedType() { + return true; + } + + @Override + public boolean isValidForVersion() { + return true; + } + + @Override + public Object moveToNextValue(Object currentValue) { + if (currentValue == null) { + return (Long) 1L; + } else { + return ((Long) currentValue) + 1L; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongStringType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongStringType.java new file mode 100644 index 0000000..4effb91 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongStringType.java @@ -0,0 +1,43 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Persists the {@link String} Java class but with more storage in the database. + * + * @author graywatson + */ +public class LongStringType extends StringType { + + private static final LongStringType singleTon = new LongStringType(); + + public static LongStringType getSingleton() { + return singleTon; + } + + private LongStringType() { + super(SqlType.LONG_STRING); + } + + /** + * Here for others to subclass. + */ + protected LongStringType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isAppropriateId() { + return false; + } + + @Override + public int getDefaultWidth() { + return 0; + } + + @Override + public Class getPrimaryClass() { + return String.class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongType.java new file mode 100644 index 0000000..a98f443 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/LongType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a long primitive. + * + * @author graywatson + */ +public class LongType extends LongObjectType { + + private static final LongType singleTon = new LongType(); + + public static LongType getSingleton() { + return singleTon; + } + + private LongType() { + super(SqlType.LONG, new Class[] { long.class }); + } + + /** + * Here for others to subclass. + */ + protected LongType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/NativeUuidType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/NativeUuidType.java new file mode 100644 index 0000000..573a05c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/NativeUuidType.java @@ -0,0 +1,30 @@ +package com.j256.ormlite.field.types; + +import java.util.UUID; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a {@link UUID} object but as a UUID type which is supported by a couple of database-types. + * + * @author graywatson + */ +public class NativeUuidType extends UuidType { + + private static final NativeUuidType singleTon = new NativeUuidType(); + + public static NativeUuidType getSingleton() { + return singleTon; + } + + private NativeUuidType() { + super(SqlType.UUID); + } + + /** + * Here for others to subclass. + */ + protected NativeUuidType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/SerializableType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/SerializableType.java new file mode 100644 index 0000000..b8d2ac5 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/SerializableType.java @@ -0,0 +1,125 @@ +package com.j256.ormlite.field.types; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.Arrays; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Persists an unknown Java Object that is {@link Serializable}. + * + * @author graywatson + */ +public class SerializableType extends BaseDataType { + + private static final SerializableType singleTon = new SerializableType(); + + public static SerializableType getSingleton() { + return singleTon; + } + + private SerializableType() { + /* + * NOTE: Serializable class should _not_ be in the list because _everything_ is serializable and we want to + * force folks to use DataType.SERIALIZABLE -- especially for forwards compatibility. + */ + super(SqlType.SERIALIZABLE); + } + + /** + * Here for others to subclass. + */ + protected SerializableType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + throw new SQLException("Default values for serializable types are not supported"); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getBytes(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + byte[] bytes = (byte[]) sqlArg; + ObjectInputStream objInStream = null; + try { + objInStream = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return objInStream.readObject(); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not read serialized object from byte array: " + Arrays.toString(bytes) + + "(len " + bytes.length + ")", e); + } finally { + // we do this to give GC a hand with ObjectInputStream reference maps + IOUtils.closeQuietly(objInStream); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) throws SQLException { + ObjectOutputStream objOutStream = null; + try { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + objOutStream = new ObjectOutputStream(outStream); + objOutStream.writeObject(obj); + objOutStream.close(); + objOutStream = null; + return outStream.toByteArray(); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not write serialized object to byte array: " + obj, e); + } finally { + // we do this to give GC a hand with ObjectOutputStream reference maps + IOUtils.closeQuietly(objOutStream); + } + } + + @Override + public boolean isValidForField(Field field) { + return Serializable.class.isAssignableFrom(field.getType()); + } + + @Override + public boolean isStreamType() { + // can't do a getObject call beforehand so we have to check for nulls + return true; + } + + @Override + public boolean isComparable() { + return false; + } + + @Override + public boolean isAppropriateId() { + return false; + } + + @Override + public boolean isArgumentHolderRequired() { + return true; + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) throws SQLException { + throw new SQLException("Serializable type cannot be converted from string to Java"); + } + + @Override + public Class getPrimaryClass() { + return Serializable.class; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortObjectType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortObjectType.java new file mode 100644 index 0000000..db94e06 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortObjectType.java @@ -0,0 +1,58 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a Short object. + * + * @author graywatson + */ +public class ShortObjectType extends BaseDataType { + + private static final ShortObjectType singleTon = new ShortObjectType(); + + public static ShortObjectType getSingleton() { + return singleTon; + } + + private ShortObjectType() { + super(SqlType.SHORT, new Class[] { Short.class }); + } + + protected ShortObjectType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return Short.parseShort(defaultStr); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return (Short) results.getShort(columnPos); + } + + @Override + public boolean isEscapedValue() { + return false; + } + + @Override + public boolean isValidForVersion() { + return true; + } + + @Override + public Object moveToNextValue(Object currentValue) { + if (currentValue == null) { + return (short) 1; + } else { + return (short) (((Short) currentValue) + 1); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortType.java new file mode 100644 index 0000000..3bbfcf6 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/ShortType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a short primitive. + * + * @author graywatson + */ +public class ShortType extends ShortObjectType { + + private static final ShortType singleTon = new ShortType(); + + public static ShortType getSingleton() { + return singleTon; + } + + private ShortType() { + super(SqlType.SHORT, new Class[] { short.class }); + } + + /** + * Here for others to subclass. + */ + protected ShortType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public boolean isPrimitive() { + return true; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/SqlDateType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/SqlDateType.java new file mode 100644 index 0000000..f8d912f --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/SqlDateType.java @@ -0,0 +1,71 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.sql.Timestamp; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a {@link java.sql.Date} object. + * + *

+ * NOTE: This is not the same as the {@link java.util.Date} class handled with {@link DateType}. If it + * recommended that you use the other Date class which is more standard to Java programs. + *

+ * + * @author graywatson + */ +public class SqlDateType extends DateType { + + private static final SqlDateType singleTon = new SqlDateType(); + private static final DateStringFormatConfig sqlDateFormatConfig = new DateStringFormatConfig("yyyy-MM-dd"); + + public static SqlDateType getSingleton() { + return singleTon; + } + + private SqlDateType() { + super(SqlType.DATE, new Class[] { java.sql.Date.class }); + } + + /** + * Here for others to subclass. + */ + protected SqlDateType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + Timestamp value = (Timestamp) sqlArg; + return new java.sql.Date(value.getTime()); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + return sqlArgToJava(fieldType, super.parseDefaultString(fieldType, defaultStr), 0); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) { + java.sql.Date date = (java.sql.Date) javaObject; + return new Timestamp(date.getTime()); + } + + @Override + protected DateStringFormatConfig getDefaultDateFormatConfig() { + return sqlDateFormatConfig; + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) { + return sqlArgToJava(fieldType, Timestamp.valueOf(stringValue), columnPos); + } + + @Override + public boolean isValidForField(Field field) { + return (field.getType() == java.sql.Date.class); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringBytesType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringBytesType.java new file mode 100644 index 0000000..308ad94 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringBytesType.java @@ -0,0 +1,98 @@ +package com.j256.ormlite.field.types; + +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a String as a byte array. + * + * @author graywatson + */ +public class StringBytesType extends BaseDataType { + + private static final String DEFAULT_STRING_BYTES_CHARSET_NAME = "Unicode"; + + private static final StringBytesType singleTon = new StringBytesType(); + + public static StringBytesType getSingleton() { + return singleTon; + } + + private StringBytesType() { + super(SqlType.BYTE_ARRAY); + } + + /** + * Here for others to subclass. + */ + protected StringBytesType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) throws SQLException { + throw new SQLException("String-bytes type cannot have default values"); + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getBytes(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + byte[] bytes = (byte[]) sqlArg; + String charsetName = getCharsetName(fieldType); + try { + // NOTE: I can't use new String(bytes, Charset) because it was introduced in 1.6. + return new String(bytes, charsetName); + } catch (UnsupportedEncodingException e) { + throw SqlExceptionUtil.create("Could not convert string with charset name: " + charsetName, e); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) throws SQLException { + String string = (String) javaObject; + String charsetName = getCharsetName(fieldType); + try { + // NOTE: I can't use string.getBytes(Charset) because it was introduced in 1.6. + return string.getBytes(charsetName); + } catch (UnsupportedEncodingException e) { + throw SqlExceptionUtil.create("Could not convert string with charset name: " + charsetName, e); + } + } + + @Override + public boolean isAppropriateId() { + return false; + } + + @Override + public boolean isArgumentHolderRequired() { + return true; + } + + @Override + public Object resultStringToJava(FieldType fieldType, String stringValue, int columnPos) throws SQLException { + throw new SQLException("String-bytes type cannot be converted from string to Java"); + } + + @Override + public Class getPrimaryClass() { + return String.class; + } + + private String getCharsetName(FieldType fieldType) { + if (fieldType == null || fieldType.getFormat() == null) { + return DEFAULT_STRING_BYTES_CHARSET_NAME; + } else { + return fieldType.getFormat(); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringType.java new file mode 100644 index 0000000..1ea364d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/StringType.java @@ -0,0 +1,50 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a String object. + * + * @author graywatson + */ +public class StringType extends BaseDataType { + + public static int DEFAULT_WIDTH = 255; + + private static final StringType singleTon = new StringType(); + + public static StringType getSingleton() { + return singleTon; + } + + private StringType() { + super(SqlType.STRING, new Class[] { String.class }); + } + + protected StringType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + protected StringType(SqlType sqlType) { + super(sqlType); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return defaultStr; + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getString(columnPos); + } + + @Override + public int getDefaultWidth() { + return DEFAULT_WIDTH; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampStringType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampStringType.java new file mode 100644 index 0000000..61943c3 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampStringType.java @@ -0,0 +1,63 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a {@link java.sql.Timestamp} object as a String. + * + * @author graywatson + */ +public class TimeStampStringType extends DateStringType { + + private static final TimeStampStringType singleTon = new TimeStampStringType(); + + public static TimeStampStringType getSingleton() { + return singleTon; + } + + private TimeStampStringType() { + super(SqlType.STRING); + } + + /** + * Here for others to subclass. + */ + protected TimeStampStringType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + Date date = (Date) super.sqlArgToJava(fieldType, sqlArg, columnPos); + return new Timestamp(date.getTime()); + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) { + Timestamp timeStamp = (Timestamp) javaObject; + return super.javaToSqlArg(fieldType, new Date(timeStamp.getTime())); + } + + @Override + public boolean isValidForField(Field field) { + return (field.getType() == java.sql.Timestamp.class); + } + + @Override + public Object moveToNextValue(Object currentValue) { + long newVal = System.currentTimeMillis(); + if (currentValue == null) { + return new java.sql.Timestamp(newVal); + } else if (newVal == ((java.sql.Timestamp) currentValue).getTime()) { + return new java.sql.Timestamp(newVal + 1L); + } else { + return new java.sql.Timestamp(newVal); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampType.java new file mode 100644 index 0000000..32746b3 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/TimeStampType.java @@ -0,0 +1,60 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Type that persists a {@link java.sql.Timestamp} object. + * + * @author graywatson + */ +public class TimeStampType extends DateType { + + private static final TimeStampType singleTon = new TimeStampType(); + + public static TimeStampType getSingleton() { + return singleTon; + } + + private TimeStampType() { + super(SqlType.DATE, new Class[] { java.sql.Timestamp.class }); + } + + /** + * Here for others to subclass. + */ + protected TimeStampType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { + // noop pass-thru + return sqlArg; + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object javaObject) { + // noop pass-thru + return javaObject; + } + + @Override + public boolean isValidForField(Field field) { + return (field.getType() == java.sql.Timestamp.class); + } + + @Override + public Object moveToNextValue(Object currentValue) { + long newVal = System.currentTimeMillis(); + if (currentValue == null) { + return new java.sql.Timestamp(newVal); + } else if (newVal == ((java.sql.Timestamp) currentValue).getTime()) { + return new java.sql.Timestamp(newVal + 1L); + } else { + return new java.sql.Timestamp(newVal); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/UuidType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/UuidType.java new file mode 100644 index 0000000..98ca904 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/UuidType.java @@ -0,0 +1,87 @@ +package com.j256.ormlite.field.types; + +import java.sql.SQLException; +import java.util.UUID; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Type that persists a {@link UUID} object. + * + * @author graywatson + */ +public class UuidType extends BaseDataType { + + public static int DEFAULT_WIDTH = 48; + + private static final UuidType singleTon = new UuidType(); + + public static UuidType getSingleton() { + return singleTon; + } + + private UuidType() { + super(SqlType.STRING, new Class[] { UUID.class }); + } + + protected UuidType(SqlType sqlType) { + super(sqlType); + } + + /** + * Here for others to subclass. + */ + protected UuidType(SqlType sqlType, Class[] classes) { + super(sqlType, classes); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return defaultStr; + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) throws SQLException { + return results.getString(columnPos); + } + + @Override + public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) throws SQLException { + String uuidStr = (String) sqlArg; + try { + return java.util.UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + throw SqlExceptionUtil.create("Problems with column " + columnPos + " parsing UUID-string '" + uuidStr + + "'", e); + } + } + + @Override + public Object javaToSqlArg(FieldType fieldType, Object obj) { + UUID uuid = (UUID) obj; + return uuid.toString(); + } + + @Override + public boolean isValidGeneratedType() { + return true; + } + + @Override + public boolean isSelfGeneratedId() { + return true; + } + + @Override + public Object generateId() { + return java.util.UUID.randomUUID(); + } + + @Override + public int getDefaultWidth() { + return DEFAULT_WIDTH; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/field/types/VoidType.java b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/VoidType.java new file mode 100644 index 0000000..f5fbfc4 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/field/types/VoidType.java @@ -0,0 +1,33 @@ +package com.j256.ormlite.field.types; + +import java.lang.reflect.Field; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Marker class used to see if we have a customer persister defined. + * + * @author graywatson + */ +public class VoidType extends BaseDataType { + + VoidType() { + super(null, new Class[] {}); + } + + @Override + public Object parseDefaultString(FieldType fieldType, String defaultStr) { + return null; + } + + @Override + public Object resultToSqlArg(FieldType fieldType, DatabaseResults results, int columnPos) { + return null; + } + + @Override + public boolean isValidForField(Field field) { + return false; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/logger/LocalLog.java b/MemeProject/app/src/main/java/com/j256/ormlite/logger/LocalLog.java new file mode 100644 index 0000000..87d7a91 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/logger/LocalLog.java @@ -0,0 +1,216 @@ +package com.j256.ormlite.logger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +import com.j256.ormlite.misc.IOUtils; + +/** + *

+ * Class which implements our {@link Log} interface so we can bypass external logging classes if they are not available. + *

+ * + *

+ * You can set the log level by setting the System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "trace"). Acceptable + * values are: TRACE, DEBUG, INFO, WARN, ERROR, and FATAL. You can also redirect the log to a file by setting the + * System.setProperty(LocalLog.LOCAL_LOG_FILE_PROPERTY, "log.out"). Otherwise, log output will go to stdout. + *

+ * + *

+ * It also supports a file ormliteLocalLog.properties file which contains lines such as: + *

+ * + *
+ * # regex-pattern = Level
+ * log4j\.logger\.com\.j256\.ormlite.*=DEBUG
+ * log4j\.logger\.com\.j256\.ormlite\.stmt\.mapped.BaseMappedStatement=TRACE
+ * log4j\.logger\.com\.j256\.ormlite\.stmt\.mapped.MappedCreate=TRACE
+ * log4j\.logger\.com\.j256\.ormlite\.stmt\.StatementExecutor=TRACE
+ * 
+ * + * @author graywatson + */ +public class LocalLog implements Log { + + public static final String LOCAL_LOG_LEVEL_PROPERTY = "com.j256.ormlite.logger.level"; + public static final String LOCAL_LOG_FILE_PROPERTY = "com.j256.ormlite.logger.file"; + public static final String LOCAL_LOG_PROPERTIES_FILE = "/ormliteLocalLog.properties"; + + private static final Level DEFAULT_LEVEL = Level.DEBUG; + private static final ThreadLocal dateFormatThreadLocal = new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); + } + }; + private static PrintStream printStream; + private static final List classLevels; + + private final String className; + private final Level level; + + static { + InputStream stream = LocalLog.class.getResourceAsStream(LOCAL_LOG_PROPERTIES_FILE); + List levels = readLevelResourceFile(stream); + classLevels = levels; + + /* + * We need to do this here otherwise each logger has their own open PrintStream to the file and the messages can + * overlap. Not good. + */ + String logPath = System.getProperty(LOCAL_LOG_FILE_PROPERTY); + openLogFile(logPath); + } + + public LocalLog(String className) { + // get the last part of the class name + this.className = LoggerFactory.getSimpleClassName(className); + + Level level = null; + if (classLevels != null) { + for (PatternLevel patternLevel : classLevels) { + if (patternLevel.pattern.matcher(className).matches()) { + // if level has not been set or the level is lower... + if (level == null || patternLevel.level.ordinal() < level.ordinal()) { + level = patternLevel.level; + } + } + } + } + + if (level == null) { + // see if we have a level set + String levelName = System.getProperty(LOCAL_LOG_LEVEL_PROPERTY); + if (levelName == null) { + level = DEFAULT_LEVEL; + } else { + Level matchedLevel; + try { + matchedLevel = Level.valueOf(levelName.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Level '" + levelName + "' was not found", e); + } + level = matchedLevel; + } + } + this.level = level; + } + + /** + * Reopen the associated static logging stream. Set to null to redirect to System.out. + */ + public static void openLogFile(String logPath) { + if (logPath == null) { + printStream = System.out; + } else { + try { + printStream = new PrintStream(new File(logPath)); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Log file " + logPath + " was not found", e); + } + } + } + + public boolean isLevelEnabled(Level level) { + return this.level.isEnabled(level); + } + + public void log(Level level, String msg) { + printMessage(level, msg, null); + } + + public void log(Level level, String msg, Throwable throwable) { + printMessage(level, msg, throwable); + } + + /** + * Flush any IO to disk. For testing purposes. + */ + void flush() { + printStream.flush(); + } + + /** + * Read in our levels from our configuration file. + */ + static List readLevelResourceFile(InputStream stream) { + List levels = null; + if (stream != null) { + try { + levels = configureClassLevels(stream); + } catch (IOException e) { + System.err.println("IO exception reading the log properties file '" + LOCAL_LOG_PROPERTIES_FILE + "': " + + e); + } finally { + IOUtils.closeQuietly(stream); + } + } + return levels; + } + + private static List configureClassLevels(InputStream stream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + List list = new ArrayList(); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + // skip empty lines or comments + if (line.length() == 0 || line.charAt(0) == '#') { + continue; + } + String[] parts = line.split("="); + if (parts.length != 2) { + System.err.println("Line is not in the format of 'pattern = level': " + line); + continue; + } + Pattern pattern = Pattern.compile(parts[0].trim()); + Level level; + try { + level = Level.valueOf(parts[1].trim()); + } catch (IllegalArgumentException e) { + System.err.println("Level '" + parts[1] + "' was not found"); + continue; + } + list.add(new PatternLevel(pattern, level)); + } + return list; + } + + private void printMessage(Level level, String message, Throwable throwable) { + if (!isLevelEnabled(level)) { + return; + } + StringBuilder sb = new StringBuilder(128); + DateFormat dateFormat = dateFormatThreadLocal.get(); + sb.append(dateFormat.format(new Date())); + sb.append(" [").append(level.name()).append("] "); + sb.append(className).append(' '); + sb.append(message); + printStream.println(sb.toString()); + if (throwable != null) { + throwable.printStackTrace(printStream); + } + } + + private static class PatternLevel { + Pattern pattern; + Level level; + public PatternLevel(Pattern pattern, Level level) { + this.pattern = pattern; + this.level = level; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/logger/Log.java b/MemeProject/app/src/main/java/com/j256/ormlite/logger/Log.java new file mode 100644 index 0000000..eeed28f --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/logger/Log.java @@ -0,0 +1,59 @@ +package com.j256.ormlite.logger; + +/** + * Interface so we can front various log code which may or may not be in the classpath. + * + * @author graywatson + */ +public interface Log { + + /** + * Returns true if the log mode is in trace or higher. + */ + public boolean isLevelEnabled(Level level); + + /** + * Log a trace message. + */ + public void log(Level level, String message); + + /** + * Log a trace message with a throwable. + */ + public void log(Level level, String message, Throwable t); + + /** + * Level of log messages being sent. + */ + public enum Level { + /** for tracing messages that are very verbose */ + TRACE(1), + /** messages suitable for debugging purposes */ + DEBUG(2), + /** information messages */ + INFO(3), + /** warning messages */ + WARNING(4), + /** error messages */ + ERROR(5), + /** severe fatal messages */ + FATAL(6), + // end + ; + + private int level; + + private Level(int level) { + this.level = level; + } + + /** + * Return whether or not a level argument is enabled for this level value. So, + * {@code Level.INFO.isEnabled(Level.WARN)} returns true but {@code Level.INFO.isEnabled(Level.DEBUG)} returns + * false. + */ + public boolean isEnabled(Level otherLevel) { + return level <= otherLevel.level; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/logger/Logger.java b/MemeProject/app/src/main/java/com/j256/ormlite/logger/Logger.java new file mode 100644 index 0000000..749369e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/logger/Logger.java @@ -0,0 +1,621 @@ +package com.j256.ormlite.logger; + +import com.j256.ormlite.logger.Log.Level; + +/** + *

+ * Class which wraps our {@link Log} interface and provides {} argument features like slf4j. It allows us to plug in + * additional log systems if necessary. + *

+ * + *

+ * NOTE: We do the (msg, arg0), (msg, arg0, arg1), (msg, arg0, arg1, arg2), and (msg, argArray) patterns because + * if we do ... for everything, we will get a new Object[] each log call which we don't want -- even if the message is + * never logged because of the log level. Also, we don't use ... at all because we want to know when we are + * creating a new Object[] so we can make sure it is what we want. I thought ... was so much better than slf4j but it + * turns out they were spot on. Sigh. + *

+ * + *

+ * NOTE: When you are using the argArray methods, you should consider wrapping the call in an {@code if} so the + * {@code Object[]} won't be created unnecessarily. + *

+ * + *
+ * if (logger.isLevelEnabled(Level...)) ...
+ * 
+ * + * @author graywatson + */ +public class Logger { + + private final static String ARG_STRING = "{}"; + private final static Object UNKNOWN_ARG = new Object(); + private final static int DEFAULT_FULL_MESSAGE_LENGTH = 128; + private final Log log; + + public Logger(Log log) { + this.log = log; + } + + /** + * Return if logging level is enabled. + */ + public boolean isLevelEnabled(Level level) { + return log.isLevelEnabled(level); + } + + /** + * Log a trace message. + */ + public void trace(String msg) { + logIfEnabled(Level.TRACE, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a trace message. + */ + public void trace(String msg, Object arg0) { + logIfEnabled(Level.TRACE, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a trace message. + */ + public void trace(String msg, Object arg0, Object arg1) { + logIfEnabled(Level.TRACE, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a trace message. + */ + public void trace(String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.TRACE, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a trace message. + */ + public void trace(String msg, Object[] argArray) { + logIfEnabled(Level.TRACE, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a trace message with a throwable. + */ + public void trace(Throwable throwable, String msg) { + logIfEnabled(Level.TRACE, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a trace message with a throwable. + */ + public void trace(Throwable throwable, String msg, Object arg0) { + logIfEnabled(Level.TRACE, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a trace message with a throwable. + */ + public void trace(Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(Level.TRACE, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a trace message with a throwable. + */ + public void trace(Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.TRACE, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a trace message with a throwable. + */ + public void trace(Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(Level.TRACE, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a debug message. + */ + public void debug(String msg) { + logIfEnabled(Level.DEBUG, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a debug message. + */ + public void debug(String msg, Object arg0) { + logIfEnabled(Level.DEBUG, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a debug message. + */ + public void debug(String msg, Object arg0, Object arg1) { + logIfEnabled(Level.DEBUG, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a debug message. + */ + public void debug(String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.DEBUG, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a debug message. + */ + public void debug(String msg, Object[] argArray) { + logIfEnabled(Level.DEBUG, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a debug message with a throwable. + */ + public void debug(Throwable throwable, String msg) { + logIfEnabled(Level.DEBUG, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a debug message with a throwable. + */ + public void debug(Throwable throwable, String msg, Object arg0) { + logIfEnabled(Level.DEBUG, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a debug message with a throwable. + */ + public void debug(Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(Level.DEBUG, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a debug message with a throwable. + */ + public void debug(Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.DEBUG, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a debug message with a throwable. + */ + public void debug(Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(Level.DEBUG, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a info message. + */ + public void info(String msg) { + logIfEnabled(Level.INFO, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a info message. + */ + public void info(String msg, Object arg0) { + logIfEnabled(Level.INFO, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a info message. + */ + public void info(String msg, Object arg0, Object arg1) { + logIfEnabled(Level.INFO, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a info message. + */ + public void info(String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.INFO, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a info message. + */ + public void info(String msg, Object[] argArray) { + logIfEnabled(Level.INFO, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a info message with a throwable. + */ + public void info(Throwable throwable, String msg) { + logIfEnabled(Level.INFO, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a info message with a throwable. + */ + public void info(Throwable throwable, String msg, Object arg0) { + logIfEnabled(Level.INFO, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a info message with a throwable. + */ + public void info(Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(Level.INFO, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a info message with a throwable. + */ + public void info(Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.INFO, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a info message with a throwable. + */ + public void info(Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(Level.INFO, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a warning message. + */ + public void warn(String msg) { + logIfEnabled(Level.WARNING, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a warning message. + */ + public void warn(String msg, Object arg0) { + logIfEnabled(Level.WARNING, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a warning message. + */ + public void warn(String msg, Object arg0, Object arg1) { + logIfEnabled(Level.WARNING, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a warning message. + */ + public void warn(String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.WARNING, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a warning message. + */ + public void warn(String msg, Object[] argArray) { + logIfEnabled(Level.WARNING, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a warning message with a throwable. + */ + public void warn(Throwable throwable, String msg) { + logIfEnabled(Level.WARNING, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a warning message with a throwable. + */ + public void warn(Throwable throwable, String msg, Object arg0) { + logIfEnabled(Level.WARNING, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a warning message with a throwable. + */ + public void warn(Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(Level.WARNING, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a warning message with a throwable. + */ + public void warn(Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.WARNING, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a warning message with a throwable. + */ + public void warn(Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(Level.WARNING, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a error message. + */ + public void error(String msg) { + logIfEnabled(Level.ERROR, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a error message. + */ + public void error(String msg, Object arg0) { + logIfEnabled(Level.ERROR, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a error message. + */ + public void error(String msg, Object arg0, Object arg1) { + logIfEnabled(Level.ERROR, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a error message. + */ + public void error(String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.ERROR, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a error message. + */ + public void error(String msg, Object[] argArray) { + logIfEnabled(Level.ERROR, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a error message with a throwable. + */ + public void error(Throwable throwable, String msg) { + logIfEnabled(Level.ERROR, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a error message with a throwable. + */ + public void error(Throwable throwable, String msg, Object arg0) { + logIfEnabled(Level.ERROR, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a error message with a throwable. + */ + public void error(Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(Level.ERROR, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a error message with a throwable. + */ + public void error(Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.ERROR, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a error message with a throwable. + */ + public void error(Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(Level.ERROR, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a fatal message. + */ + public void fatal(String msg) { + logIfEnabled(Level.FATAL, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a fatal message. + */ + public void fatal(String msg, Object arg0) { + logIfEnabled(Level.FATAL, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a fatal message. + */ + public void fatal(String msg, Object arg0, Object arg1) { + logIfEnabled(Level.FATAL, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a fatal message. + */ + public void fatal(String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.FATAL, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a fatal message. + */ + public void fatal(String msg, Object[] argArray) { + logIfEnabled(Level.FATAL, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a fatal message with a throwable. + */ + public void fatal(Throwable throwable, String msg) { + logIfEnabled(Level.FATAL, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a fatal message with a throwable. + */ + public void fatal(Throwable throwable, String msg, Object arg0) { + logIfEnabled(Level.FATAL, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a fatal message with a throwable. + */ + public void fatal(Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(Level.FATAL, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a fatal message with a throwable. + */ + public void fatal(Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(Level.FATAL, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a fatal message with a throwable. + */ + public void fatal(Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(Level.FATAL, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a message at the provided level. + */ + public void log(Level level, String msg) { + logIfEnabled(level, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a message at the provided level. + */ + public void log(Level level, String msg, Object arg0) { + logIfEnabled(level, null, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a message at the provided level. + */ + public void log(Level level, String msg, Object arg0, Object arg1) { + logIfEnabled(level, null, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a message at the provided level. + */ + public void log(Level level, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(level, null, msg, arg0, arg1, arg2, null); + } + + /** + * Log a message at the provided level. + */ + public void log(Level level, String msg, Object[] argArray) { + logIfEnabled(level, null, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + /** + * Log a message with a throwable at the provided level. + */ + public void log(Level level, Throwable throwable, String msg) { + logIfEnabled(level, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a message with a throwable at the provided level. + */ + public void log(Level level, Throwable throwable, String msg, Object arg0) { + logIfEnabled(level, throwable, msg, arg0, UNKNOWN_ARG, UNKNOWN_ARG, null); + } + + /** + * Log a message with a throwable at the provided level. + */ + public void log(Level level, Throwable throwable, String msg, Object arg0, Object arg1) { + logIfEnabled(level, throwable, msg, arg0, arg1, UNKNOWN_ARG, null); + } + + /** + * Log a message with a throwable at the provided level. + */ + public void log(Level level, Throwable throwable, String msg, Object arg0, Object arg1, Object arg2) { + logIfEnabled(level, throwable, msg, arg0, arg1, arg2, null); + } + + /** + * Log a message with a throwable at the provided level. + */ + public void log(Level level, Throwable throwable, String msg, Object[] argArray) { + logIfEnabled(level, throwable, msg, UNKNOWN_ARG, UNKNOWN_ARG, UNKNOWN_ARG, argArray); + } + + private void logIfEnabled(Level level, Throwable throwable, String msg, Object arg0, Object arg1, Object arg2, + Object[] argArray) { + if (log.isLevelEnabled(level)) { + String fullMsg = buildFullMessage(msg, arg0, arg1, arg2, argArray); + if (throwable == null) { + log.log(level, fullMsg); + } else { + log.log(level, fullMsg, throwable); + } + } + } + + /** + * Return a combined single message from the msg (with possible {}) and optional arguments. + */ + private String buildFullMessage(String msg, Object arg0, Object arg1, Object arg2, Object[] argArray) { + StringBuilder sb = null; + int lastIndex = 0; + int argC = 0; + while (true) { + int argIndex = msg.indexOf(ARG_STRING, lastIndex); + // no more {} arguments? + if (argIndex == -1) { + break; + } + if (sb == null) { + // we build this lazily in case there is no {} in the msg + sb = new StringBuilder(DEFAULT_FULL_MESSAGE_LENGTH); + } + // add the string before the arg-string + sb.append(msg, lastIndex, argIndex); + // shift our last-index past the arg-string + lastIndex = argIndex + ARG_STRING.length(); + // add the argument, if we still have any + if (argArray == null) { + if (argC == 0) { + appendArg(sb, arg0); + } else if (argC == 1) { + appendArg(sb, arg1); + } else if (argC == 2) { + appendArg(sb, arg2); + } else { + // we have too many {} so we just ignore them + } + } else if (argC < argArray.length) { + appendArg(sb, argArray[argC]); + } else { + // we have too many {} so we just ignore them + } + argC++; + } + if (sb == null) { + // if we have yet to create a StringBuilder then just append the msg which has no {} + return msg; + } else { + // spit out the end of the msg + sb.append(msg, lastIndex, msg.length()); + return sb.toString(); + } + } + + private void appendArg(StringBuilder sb, Object arg) { + if (arg == UNKNOWN_ARG) { + // ignore it + } else if (arg == null) { + // this is what sb.append(null) does + sb.append("null"); + } else if (arg.getClass().isArray()) { + // we do a special thing if we have an array argument + Object[] array = (Object[]) arg; + sb.append('['); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(array[i]); + } + sb.append(']'); + } else { + // might as well to the toString here because we know it isn't null + sb.append(arg.toString()); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/logger/LoggerFactory.java b/MemeProject/app/src/main/java/com/j256/ormlite/logger/LoggerFactory.java new file mode 100644 index 0000000..b0bef9d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/logger/LoggerFactory.java @@ -0,0 +1,173 @@ +package com.j256.ormlite.logger; + +import java.lang.reflect.Constructor; + +import com.j256.ormlite.logger.Log.Level; + +/** + * Factory that creates {@link Logger} instances. It uses reflection to see what loggers are installed on the system and + * tries to find the most appropriate one. + * + *

+ * To set the logger to a particular type, set the system property ("com.j256.ormlite.logger.type") contained in + * {@link #LOG_TYPE_SYSTEM_PROPERTY} to be one of the values in LogType enum. + *

+ */ +public class LoggerFactory { + + public static final String LOG_TYPE_SYSTEM_PROPERTY = "com.j256.ormlite.logger.type"; + private static LogType logType; + + /** + * For static calls only. + */ + private LoggerFactory() { + } + + /** + * Return a logger associated with a particular class. + */ + public static Logger getLogger(Class clazz) { + return getLogger(clazz.getName()); + } + + /** + * Return a logger associated with a particular class name. + */ + public static Logger getLogger(String className) { + if (logType == null) { + logType = findLogType(); + } + return new Logger(logType.createLog(className)); + } + + /** + * Return the single class name from a class-name string. + */ + public static String getSimpleClassName(String className) { + // get the last part of the class name + String[] parts = className.split("\\."); + if (parts.length <= 1) { + return className; + } else { + return parts[parts.length - 1]; + } + } + + /** + * Return the most appropriate log type. This should _never_ return null. + */ + private static LogType findLogType() { + + // see if the log-type was specified as a system property + String logTypeString = System.getProperty(LOG_TYPE_SYSTEM_PROPERTY); + if (logTypeString != null) { + try { + return LogType.valueOf(logTypeString); + } catch (IllegalArgumentException e) { + Log log = new LocalLog(LoggerFactory.class.getName()); + log.log(Level.WARNING, "Could not find valid log-type from system property '" + + LOG_TYPE_SYSTEM_PROPERTY + "', value '" + logTypeString + "'"); + } + } + + for (LogType logType : LogType.values()) { + if (logType.isAvailable()) { + return logType; + } + } + // fall back is always LOCAL, never reached + return LogType.LOCAL; + } + + /** + * Type of internal logs supported. This is package permissions for testing. + */ + public enum LogType { + SLF4J("org.slf4j.LoggerFactory", "com.j256.ormlite.logger.Slf4jLoggingLog"), + /** + * WARNING: Android log must be _before_ commons logging since Android provides commons logging but logging + * messages are ignored that are sent there. Grrrrr. + */ + ANDROID("android.util.Log", "com.j256.ormlite.android.AndroidLog"), + COMMONS_LOGGING("org.apache.commons.logging.LogFactory", "com.j256.ormlite.logger.CommonsLoggingLog"), + LOG4J2("org.apache.logging.log4j.LogManager", "com.j256.ormlite.logger.Log4j2Log"), + LOG4J("org.apache.log4j.Logger", "com.j256.ormlite.logger.Log4jLog"), + // this should always be at the end, arguments are unused + LOCAL(LocalLog.class.getName(), LocalLog.class.getName()) { + @Override + public Log createLog(String classLabel) { + return new LocalLog(classLabel); + } + @Override + public boolean isAvailable() { + // always available + return true; + } + }, + // end + ; + + private final String detectClassName; + private final String logClassName; + + private LogType(String detectClassName, String logClassName) { + this.detectClassName = detectClassName; + this.logClassName = logClassName; + } + + /** + * Create and return a Log class for this type. + */ + public Log createLog(String classLabel) { + try { + return createLogFromClassName(classLabel); + } catch (Exception e) { + // oh well, fall back to the local log + Log log = new LocalLog(classLabel); + log.log(Level.WARNING, "Unable to call constructor with single String argument for class " + + logClassName + ", so had to use local log: " + e.getMessage()); + return log; + } + } + + /** + * Return true if the log class is available. + */ + public boolean isAvailable() { + if (!isAvailableTestClass()) { + return false; + } + try { + // try to actually use the logger which resolves problems with the Android stub + Log log = createLogFromClassName(getClass().getName()); + log.isLevelEnabled(Level.INFO); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Try to create the log from the class name which may throw. + */ + Log createLogFromClassName(String classLabel) throws Exception { + Class clazz = Class.forName(logClassName); + @SuppressWarnings("unchecked") + Constructor constructor = (Constructor) clazz.getConstructor(String.class); + return constructor.newInstance(classLabel); + } + + /** + * This is package permissions for testing purposes. + */ + boolean isAvailableTestClass() { + try { + Class.forName(detectClassName); + return true; + } catch (Exception e) { + return false; + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/logger/Slf4jLoggingLog.java b/MemeProject/app/src/main/java/com/j256/ormlite/logger/Slf4jLoggingLog.java new file mode 100644 index 0000000..d52fd79 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/logger/Slf4jLoggingLog.java @@ -0,0 +1,86 @@ +package com.j256.ormlite.logger; + +/** + * Class which implements our {@link com.j256.ormlite.logger.Log} interface by delegating to slf4j. + * + * @author graywatson + */ +public class Slf4jLoggingLog implements Log { + + private final org.slf4j.Logger logger; + + public Slf4jLoggingLog(String className) { + this.logger = org.slf4j.LoggerFactory.getLogger(className); + } + + public boolean isLevelEnabled(Level level) { + switch (level) { + case TRACE : + return logger.isTraceEnabled(); + case DEBUG : + return logger.isDebugEnabled(); + case INFO : + return logger.isInfoEnabled(); + case WARNING : + return logger.isWarnEnabled(); + case ERROR : + return logger.isErrorEnabled(); + case FATAL : + return logger.isErrorEnabled(); + default : + return logger.isInfoEnabled(); + } + } + + public void log(Level level, String msg) { + switch (level) { + case TRACE : + logger.trace(msg); + break; + case DEBUG : + logger.debug(msg); + break; + case INFO : + logger.info(msg); + break; + case WARNING : + logger.warn(msg); + break; + case ERROR : + logger.error(msg); + break; + case FATAL : + logger.error(msg); + break; + default : + logger.info(msg); + break; + } + } + + public void log(Level level, String msg, Throwable t) { + switch (level) { + case TRACE : + logger.trace(msg, t); + break; + case DEBUG : + logger.debug(msg, t); + break; + case INFO : + logger.info(msg, t); + break; + case WARNING : + logger.warn(msg, t); + break; + case ERROR : + logger.error(msg, t); + break; + case FATAL : + logger.error(msg, t); + break; + default : + logger.info(msg, t); + break; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/misc/BaseDaoEnabled.java b/MemeProject/app/src/main/java/com/j256/ormlite/misc/BaseDaoEnabled.java new file mode 100644 index 0000000..7921fef --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/misc/BaseDaoEnabled.java @@ -0,0 +1,136 @@ +package com.j256.ormlite.misc; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.Dao; + +/** + * Base class that your data elements can extend which allow them to refresh, update, etc. themselves. ORMLite will + * automagically set the appropriate {@link Dao} on the class if it is received by a query but if you are trying to + * create the class, you will need to either create it through the DAO or set the dao on it directly with + * {@link #setDao(Dao)}. + * + *

+ * NOTE: The default pattern is to use the {@link Dao} classes to operate on your data classes. This will allow + * your data classes to have their own hierarchy and isolates the database code in the Daos. However, you are free to + * use this base class if you prefer this pattern. + *

+ * + *

+ * NOTE: The internal Dao field has been marked with transient so that it won't be serialized (thanks jc). If you + * do de-serialize on these classes, you will need to refresh it with the Dao to get it to work again. + *

+ * + * @author graywatson + */ +public abstract class BaseDaoEnabled { + + protected transient Dao dao; + + /** + * A call through to the {@link Dao#create(Object)}. + */ + public int create() throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.create(t); + } + + /** + * A call through to the {@link Dao#refresh(Object)}. + */ + public int refresh() throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.refresh(t); + } + + /** + * A call through to the {@link Dao#update(Object)}. + */ + public int update() throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.update(t); + } + + /** + * A call through to the {@link Dao#updateId(Object, Object)}. + */ + public int updateId(ID newId) throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.updateId(t, newId); + } + + /** + * A call through to the {@link Dao#delete(Object)}. + */ + public int delete() throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.delete(t); + } + + /** + * A call through to the {@link Dao#objectToString(Object)}. + */ + public String objectToString() { + try { + checkForDao(); + } catch (SQLException e) { + throw new IllegalArgumentException(e); + } + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.objectToString(t); + } + + /** + * A call through to the {@link Dao#extractId(Object)}. + */ + public ID extractId() throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.extractId(t); + } + + /** + * A call through to the {@link Dao#objectsEqual(Object, Object)}. + */ + public boolean objectsEqual(T other) throws SQLException { + checkForDao(); + @SuppressWarnings("unchecked") + T t = (T) this; + return dao.objectsEqual(t, other); + } + + /** + * Set the {@link Dao} on the object. For the {@link #create()} call to work, this must be done beforehand by the + * caller. If the object has been received from a query call to the Dao then this should have been set + * automagically. + */ + public void setDao(Dao dao) { + this.dao = dao; + } + + /** + * Return the DAO object associated with this object. It will be null unless the object was generated by a query + * call to the database or {@link #setDao(Dao)} has already been called. + */ + public Dao getDao() { + return dao; + } + + private void checkForDao() throws SQLException { + if (dao == null) { + throw new SQLException("Dao has not been set on " + getClass() + " object: " + this); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/misc/IOUtils.java b/MemeProject/app/src/main/java/com/j256/ormlite/misc/IOUtils.java new file mode 100644 index 0000000..58ccb4e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/misc/IOUtils.java @@ -0,0 +1,39 @@ +package com.j256.ormlite.misc; + +import java.io.Closeable; +import java.io.IOException; +import java.sql.SQLException; + +/** + * Utility class + * + * @author graywatson + */ +public class IOUtils { + + /** + * Close the closeable if not null and ignore any exceptions. + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + // ignored + } + } + } + + /** + * Close it and ignore any exceptions. + */ + public static void closeThrowSqlException(Closeable closeable, String label) throws SQLException { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + throw SqlExceptionUtil.create("could not close " + label, e); + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/misc/JavaxPersistenceConfigurer.java b/MemeProject/app/src/main/java/com/j256/ormlite/misc/JavaxPersistenceConfigurer.java new file mode 100644 index 0000000..90f6bf3 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/misc/JavaxPersistenceConfigurer.java @@ -0,0 +1,27 @@ +package com.j256.ormlite.misc; + +import java.lang.reflect.Field; +import java.sql.SQLException; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DatabaseFieldConfig; + +/** + * Interface that allows us to load and process javax.persistance annotations only if available. + * + * @author graywatson + */ +public interface JavaxPersistenceConfigurer { + + /** + * Create and return a field config from the javax.persistence annotations associated with the field argument or + * null if no annotations present. + */ + public DatabaseFieldConfig createFieldConfig(DatabaseType databaseType, Field field) throws SQLException; + + /** + * Return the javax.persistence.Entity annotation name for the class argument or null if none or if there was no + * entity name. + */ + public String getEntityName(Class clazz); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/misc/SqlExceptionUtil.java b/MemeProject/app/src/main/java/com/j256/ormlite/misc/SqlExceptionUtil.java new file mode 100644 index 0000000..c21e3e7 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/misc/SqlExceptionUtil.java @@ -0,0 +1,32 @@ +package com.j256.ormlite.misc; + +import java.sql.SQLException; + +/** + * Utility class to help with SQLException throwing. + * + * @author graywatson + */ +public class SqlExceptionUtil { + + /** + * Should be used in a static context only. + */ + private SqlExceptionUtil() { + } + + /** + * Convenience method to allow a cause. Grrrr. + */ + public static SQLException create(String message, Throwable cause) { + SQLException sqlException; + if (cause instanceof SQLException) { + // if the cause is another SQLException, pass alot of the SQL state + sqlException = new SQLException(message, ((SQLException) cause).getSQLState()); + } else { + sqlException = new SQLException(message); + } + sqlException.initCause(cause); + return sqlException; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/misc/TransactionManager.java b/MemeProject/app/src/main/java/com/j256/ormlite/misc/TransactionManager.java new file mode 100644 index 0000000..fd5f7e8 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/misc/TransactionManager.java @@ -0,0 +1,256 @@ +package com.j256.ormlite.misc; + +import java.sql.SQLException; +import java.sql.Savepoint; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; + +/** + *

+ * Provides basic transaction support for a {@link ConnectionSource}. + *

+ * + *

+ * NOTE: For transactions to work, the database being used must support the functionality. + *

+ * + *

+ * NOTE: If you are using the Spring type wiring in Java, {@link #initialize} should be called after all of the + * set methods. In Spring XML, init-method="initialize" should be used. + *

+ * + *

+ * You can call this as an instance with a new TransactionManager(dataSource); or you can call it as a static like the + * below example: + *

+ * + *
+ * TransactionManager.callInTransaction(dataSource, new Callable<Void>() {
+ * 	public Void call() throws Exception {
+ * 		// delete both objects but make sure that if either one fails, the transaction is rolled back
+ * 		// and both objects are "restored" to the database
+ * 		fooDao.delete(foo);
+ * 		barDao.delete(bar);
+ * 		return null;
+ * 	}
+ * });
+ * 
+ * + *

+ * For Spring wiring of a Transaction Manager bean, we would do something like the following: + *

+ * + *
+ * <bean id="transactionManager" class="com.j256.ormlite.misc.TransactionManager" init-method="initialize">
+ * 	<property name="dataSource" ref="dataSource" />
+ * </bean>
+ * 
+ * + *

+ * WARNING: For most of the methods in this class, it is up to you to properly synchronize them if multiple threads are + * using a single database connection -- this includes a connection-source which works gives out a single-connection. + * The reason why this is necessary is that multiple operations are performed on the connection and race-conditions will + * exist with multiple threads working on the same connection. + *

+ * + * @author graywatson + */ +public class TransactionManager { + + private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class); + private static final String SAVE_POINT_PREFIX = "ORMLITE"; + + private ConnectionSource connectionSource; + private static AtomicInteger savePointCounter = new AtomicInteger(); + + /** + * Constructor for Spring type wiring if you are using the set methods. + */ + public TransactionManager() { + // for spring wiring -- must call setDataSource() + } + + /** + * Constructor for direct java code wiring. + */ + public TransactionManager(ConnectionSource connectionSource) { + this.connectionSource = connectionSource; + initialize(); + } + + /** + * If you are using the Spring type wiring, this should be called after all of the set methods. + */ + public void initialize() { + if (connectionSource == null) { + throw new IllegalStateException("dataSource was not set on " + getClass().getSimpleName()); + } + } + + /** + * Execute the {@link Callable} class inside of a transaction. If the callable returns then the transaction is + * committed. If the callable throws an exception then the transaction is rolled back and a {@link SQLException} is + * thrown by this method. + * + *

+ * NOTE: If your callable block really doesn't have a return object then use the Void class and return null + * from the call method. + *

+ * + *

+ * WARNING: it is up to you to properly synchronize around this method if multiple threads are using a + * connection-source which works gives out a single-connection. The reason why this is necessary is that multiple + * operations are performed on the connection and race-conditions will exist with multiple threads working on the + * same connection. + *

+ * + * @param callable + * Callable to execute inside of the transaction. + * @return The object returned by the callable. + * @throws SQLException + * If the callable threw an exception then the transaction is rolled back and a SQLException wraps the + * callable exception and is thrown by this method. + */ + public T callInTransaction(final Callable callable) throws SQLException { + return callInTransaction(connectionSource, callable); + } + + /** + * Same as {@link #callInTransaction(Callable)} except as a static method with a connection source. + * + *

+ * WARNING: it is up to you to properly synchronize around this method if multiple threads are using a + * connection-source which works gives out a single-connection. The reason why this is necessary is that multiple + * operations are performed on the connection and race-conditions will exist with multiple threads working on the + * same connection. + *

+ */ + public static T callInTransaction(final ConnectionSource connectionSource, final Callable callable) + throws SQLException { + + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + boolean saved = connectionSource.saveSpecialConnection(connection); + return callInTransaction(connection, saved, connectionSource.getDatabaseType(), callable); + } finally { + // we should clear aggressively + connectionSource.clearSpecialConnection(connection); + connectionSource.releaseConnection(connection); + } + } + + /** + * Same as {@link #callInTransaction(Callable)} except as a static method on a connection with database-type. + * + *

+ * WARNING: it is up to you to properly synchronize around this method if multiple threads are using the same + * database connection or if your connection-source is single-connection. The reason why this is necessary is that + * multiple operations are performed on the connection and race-conditions will exist with multiple threads working + * on the same connection. + *

+ */ + public static T callInTransaction(final DatabaseConnection connection, final DatabaseType databaseType, + final Callable callable) throws SQLException { + return callInTransaction(connection, false, databaseType, callable); + } + + /** + * Same as {@link #callInTransaction(Callable)} except as a static method on a connection with database-type. + * + *

+ * WARNING: it is up to you to properly synchronize around this method if multiple threads are using the same + * database connection or if your connection-source is single-connection. The reason why this is necessary is that + * multiple operations are performed on the connection and race-conditions will exist with multiple threads working + * on the same connection. + *

+ */ + public static T callInTransaction(final DatabaseConnection connection, boolean saved, + final DatabaseType databaseType, final Callable callable) throws SQLException { + + boolean restoreAutoCommit = false; + try { + boolean hasSavePoint = false; + Savepoint savePoint = null; + if (saved || databaseType.isNestedSavePointsSupported()) { + if (connection.isAutoCommitSupported()) { + if (connection.isAutoCommit()) { + // disable auto-commit mode if supported and enabled at start + connection.setAutoCommit(false); + restoreAutoCommit = true; + logger.debug("had to set auto-commit to false"); + } + } + savePoint = connection.setSavePoint(SAVE_POINT_PREFIX + savePointCounter.incrementAndGet()); + if (savePoint == null) { + logger.debug("started savePoint transaction"); + } else { + logger.debug("started savePoint transaction {}", savePoint.getSavepointName()); + } + hasSavePoint = true; + } + try { + T result = callable.call(); + if (hasSavePoint) { + commit(connection, savePoint); + } + return result; + } catch (SQLException e) { + if (hasSavePoint) { + try { + rollBack(connection, savePoint); + } catch (SQLException e2) { + logger.error(e, "after commit exception, rolling back to save-point also threw exception"); + // we continue to throw the commit exception + } + } + throw e; + } catch (Exception e) { + if (hasSavePoint) { + try { + rollBack(connection, savePoint); + } catch (SQLException e2) { + logger.error(e, "after commit exception, rolling back to save-point also threw exception"); + // we continue to throw the commit exception + } + } + throw SqlExceptionUtil.create("Transaction callable threw non-SQL exception", e); + } + } finally { + if (restoreAutoCommit) { + // try to restore if we are in auto-commit mode + connection.setAutoCommit(true); + logger.debug("restored auto-commit to true"); + } + } + } + + public void setConnectionSource(ConnectionSource connectionSource) { + this.connectionSource = connectionSource; + } + + private static void commit(DatabaseConnection connection, Savepoint savePoint) throws SQLException { + String name = (savePoint == null ? null : savePoint.getSavepointName()); + connection.commit(savePoint); + if (name == null) { + logger.debug("committed savePoint transaction"); + } else { + logger.debug("committed savePoint transaction {}", name); + } + } + + private static void rollBack(DatabaseConnection connection, Savepoint savePoint) throws SQLException { + String name = (savePoint == null ? null : savePoint.getSavepointName()); + connection.rollback(savePoint); + if (name == null) { + logger.debug("rolled back savePoint transaction"); + } else { + logger.debug("rolled back savePoint transaction {}", name); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/misc/VersionUtils.java b/MemeProject/app/src/main/java/com/j256/ormlite/misc/VersionUtils.java new file mode 100644 index 0000000..8dbe78a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/misc/VersionUtils.java @@ -0,0 +1,84 @@ +package com.j256.ormlite.misc; + +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; + +/** + * A class which helps us verify that we are running symmetric versions. + * + * @author graywatson + */ +public class VersionUtils { + + private static final String CORE_VERSION = "VERSION__4.49-SNAPSHOT__"; + + private static Logger logger; + private static boolean thrownOnErrors = false; + private static String coreVersion = CORE_VERSION; + + private VersionUtils() { + // only for static methods + } + + /** + * Verifies that the ormlite-core and -jdbc version files hold the same string. + */ + public static final void checkCoreVersusJdbcVersions(String jdbcVersion) { + logVersionWarnings("core", coreVersion, "jdbc", jdbcVersion); + } + + /** + * Verifies that the ormlite-core and -android version files hold the same string. + */ + public static final void checkCoreVersusAndroidVersions(String androidVersion) { + logVersionWarnings("core", coreVersion, "android", androidVersion); + } + + public static String getCoreVersion() { + return coreVersion; + } + + /** + * For testing purposes. + */ + static void setThrownOnErrors(boolean thrownOnErrors) { + VersionUtils.thrownOnErrors = thrownOnErrors; + } + + /** + * Log error information + */ + private static void logVersionWarnings(String label1, String version1, String label2, String version2) { + if (version1 == null) { + if (version2 != null) { + warning(null, "Unknown version", " for {}, version for {} is '{}'", new Object[] { label1, label2, + version2 }); + } + } else { + if (version2 == null) { + warning(null, "Unknown version", " for {}, version for {} is '{}'", new Object[] { label2, label1, + version1 }); + } else if (!version1.equals(version2)) { + warning(null, "Mismatched versions", ": {} is '{}', while {} is '{}'", new Object[] { label1, version1, + label2, version2 }); + } + } + } + + private static void warning(Throwable th, String msg, String format, Object[] args) { + getLogger().warn(th, msg + format, args); + if (VersionUtils.thrownOnErrors) { + throw new IllegalStateException("See error log for details:" + msg); + } + } + + /** + * Get the logger for the class. We do this so we don't have to create it all of the time. + */ + private static Logger getLogger() { + if (logger == null) { + logger = LoggerFactory.getLogger(VersionUtils.class); + } + return logger; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ArgumentHolder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ArgumentHolder.java new file mode 100644 index 0000000..de10749 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ArgumentHolder.java @@ -0,0 +1,59 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * An argument to a select SQL statement. After the query is constructed, the caller can set the value on this argument + * and run the query. Then the argument can be set again and the query re-executed. This is equivalent in SQL to a ? + * argument. + * + * @author graywatson + */ +public interface ArgumentHolder { + + /** + * Return the column-name associated with this argument. The name is set by the package internally. + */ + public String getColumnName(); + + /** + * Used internally by the package to set the column-name associated with this argument. + */ + public void setMetaInfo(String columnName); + + /** + * Used internally by the package to set the fieldType associated with this argument. + */ + public void setMetaInfo(FieldType fieldType); + + /** + * Used internally by the package to set the column-name and fieldType associated with this argument. + */ + public void setMetaInfo(String columnName, FieldType fieldType); + + /** + * Set the value associated with this argument. The value should be set by the user after the query has been built + * but before it has been executed. + */ + public void setValue(Object value); + + /** + * Return the value associated with this argument suitable for passing to SQL. The value should be set by the user + * before it is consumed. + */ + public Object getSqlArgValue() throws SQLException; + + /** + * Return the SQL type associated with this class. Either this or the field-type must be available. + */ + public SqlType getSqlType(); + + /** + * Return the field type associated with this class. Either this or the sql-type must be available. The field-type + * is available if there is a corresponding column-name set on the holder. + */ + public FieldType getFieldType(); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/BaseArgumentHolder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/BaseArgumentHolder.java new file mode 100644 index 0000000..402e202 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/BaseArgumentHolder.java @@ -0,0 +1,118 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * Base class for other select argument classes. + * + * @author graywatson + */ +public abstract class BaseArgumentHolder implements ArgumentHolder { + + private String columnName = null; + private FieldType fieldType = null; + private SqlType sqlType = null; + + public BaseArgumentHolder() { + // no args + } + + public BaseArgumentHolder(String columName) { + this.columnName = columName; + } + + public BaseArgumentHolder(SqlType sqlType) { + this.sqlType = sqlType; + } + + /** + * Return the stored value. + */ + protected abstract Object getValue(); + + public abstract void setValue(Object value); + + /** + * Return true if the value is set. + */ + protected abstract boolean isValueSet(); + + public String getColumnName() { + return columnName; + } + + public void setMetaInfo(String columnName) { + if (this.columnName == null) { + // not set yet + } else if (this.columnName.equals(columnName)) { + // set to the same value as before + } else { + throw new IllegalArgumentException("Column name cannot be set twice from " + this.columnName + " to " + + columnName + ". Using a SelectArg twice in query with different columns?"); + } + this.columnName = columnName; + } + + public void setMetaInfo(FieldType fieldType) { + if (this.fieldType == null) { + // not set yet + } else if (this.fieldType == fieldType) { + // set to the same value as before + } else { + throw new IllegalArgumentException("FieldType name cannot be set twice from " + this.fieldType + " to " + + fieldType + ". Using a SelectArg twice in query with different columns?"); + } + this.fieldType = fieldType; + } + + public void setMetaInfo(String columnName, FieldType fieldType) { + setMetaInfo(columnName); + setMetaInfo(fieldType); + } + + public Object getSqlArgValue() throws SQLException { + if (!isValueSet()) { + throw new SQLException("Column value has not been set for " + columnName); + } + Object value = getValue(); + if (value == null) { + return null; + } else if (fieldType == null) { + return value; + } else if (fieldType.isForeign() && fieldType.getType() == value.getClass()) { + FieldType idFieldType = fieldType.getForeignIdField(); + return idFieldType.extractJavaFieldValue(value); + } else { + return fieldType.convertJavaFieldToSqlArgValue(value); + } + } + + public FieldType getFieldType() { + return fieldType; + } + + public SqlType getSqlType() { + return sqlType; + } + + @Override + public String toString() { + if (!isValueSet()) { + return "[unset]"; + } + Object val; + try { + val = getSqlArgValue(); + if (val == null) { + return "[null]"; + } else { + return val.toString(); + } + } catch (SQLException e) { + return "[could not get value: " + e + "]"; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ColumnArg.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ColumnArg.java new file mode 100644 index 0000000..d13bb5a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ColumnArg.java @@ -0,0 +1,44 @@ +package com.j256.ormlite.stmt; + +/** + * An argument to a select SQL statement that represents a column in a table. This allows you using the + * {@link QueryBuilder} to be able to compare two database fields or using {@link QueryBuilder#join(QueryBuilder)} to be + * able to compare fields in different tables. + * + *

+ * NOTE: This does not verify that the two fields in question _can_ be compared via SQL. If you try to compare + * (for example) a string to a number, a SQL exception will most likely be generated. + *

+ * + * @author graywatson + */ +public class ColumnArg { + + private final String tableName; + private final String columnName; + + /** + * For queries where only one table is being addressed. This will output an escaped column-name only into the query. + */ + public ColumnArg(String columnName) { + this.tableName = null; + this.columnName = columnName; + } + + /** + * For queries where multiple tables are being addressed. This will output an escaped table-name, then a period, + * then escaped column-name only into the query. + */ + public ColumnArg(String tableName, String columnName) { + this.tableName = tableName; + this.columnName = columnName; + } + + public String getTableName() { + return tableName; + } + + public String getColumnName() { + return columnName; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/DeleteBuilder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/DeleteBuilder.java new file mode 100644 index 0000000..6b55428 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/DeleteBuilder.java @@ -0,0 +1,69 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.table.TableInfo; + +/** + * Assists in building sql DELETE statements for a particular table in a particular database. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public class DeleteBuilder extends StatementBuilder { + + // NOTE: any fields here should be added to the clear() method below + + public DeleteBuilder(DatabaseType databaseType, TableInfo tableInfo, Dao dao) { + super(databaseType, tableInfo, dao, StatementType.DELETE); + } + + /** + * Build and return a prepared delete that can be used by {@link Dao#delete(PreparedDelete)} method. If you change + * the where or make other calls you will need to re-call this method to re-prepare the statement for execution. + */ + public PreparedDelete prepare() throws SQLException { + return super.prepareStatement(null); + } + + /** + * A short cut to {@link Dao#delete(PreparedDelete)}. + */ + public int delete() throws SQLException { + return dao.delete(prepare()); + } + + /** + * @deprecated Renamed to be {@link #reset()}. + */ + @Deprecated + @Override + public void clear() { + reset(); + } + + @Override + public void reset() { + // NOTE: this is here because it is in the other sub-classes + super.reset(); + } + + @Override + protected void appendStatementStart(StringBuilder sb, List argList) { + sb.append("DELETE FROM "); + databaseType.appendEscapedEntityName(sb, tableInfo.getTableName()); + sb.append(' '); + } + + @Override + protected void appendStatementEnd(StringBuilder sb, List argList) { + // noop + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/GenericRowMapper.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/GenericRowMapper.java new file mode 100644 index 0000000..b6a5470 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/GenericRowMapper.java @@ -0,0 +1,26 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; + +import com.j256.ormlite.support.DatabaseResults; + +/** + * Parameterized version similar to Spring's RowMapper which converts a result row into an object. + * + * @param + * Type that the mapRow returns. + * @author graywatson + */ +public interface GenericRowMapper { + + /** + * Used to convert a results row to an object. + * + * @return The created object with all of the fields set from the results; + * @param results + * Results object we are mapping. + * @throws SQLException + * If we could not get the SQL results or instantiate the object. + */ + public T mapRow(DatabaseResults results) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/NullArgHolder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/NullArgHolder.java new file mode 100644 index 0000000..6073a32 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/NullArgHolder.java @@ -0,0 +1,50 @@ +package com.j256.ormlite.stmt; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; + +/** + * An argument to a select SQL statement for null arguments. This overrides the protections around multiple columns + * since it will always have a null value. + * + * @author graywatson + */ +public class NullArgHolder implements ArgumentHolder { + + public NullArgHolder() { + // typical that matches all columns/types + } + + public String getColumnName() { + return "null-holder"; + } + + public void setValue(Object value) { + throw new UnsupportedOperationException("Cannot set null on " + getClass()); + } + + public void setMetaInfo(String columnName) { + // noop + } + + public void setMetaInfo(FieldType fieldType) { + // noop + } + + public void setMetaInfo(String columnName, FieldType fieldType) { + // noop + } + + public Object getSqlArgValue() { + return null; + } + + public SqlType getSqlType() { + // we use this as our default because it should work with all SQL engines + return SqlType.STRING; + } + + public FieldType getFieldType() { + return null; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedDelete.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedDelete.java new file mode 100644 index 0000000..bb08984 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedDelete.java @@ -0,0 +1,14 @@ +package com.j256.ormlite.stmt; + +import com.j256.ormlite.dao.Dao; + +/** + * Interface returned by the {@link DeleteBuilder#prepare()} which supports custom DELETE statements. This should be in + * turn passed to the {@link Dao#delete(PreparedDelete)} method. + * + * @param + * The class that the code will be operating on. + * @author graywatson + */ +public interface PreparedDelete extends PreparedStmt { +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedQuery.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedQuery.java new file mode 100644 index 0000000..ffbd0b6 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedQuery.java @@ -0,0 +1,14 @@ +package com.j256.ormlite.stmt; + +import com.j256.ormlite.dao.Dao; + +/** + * Interface returned by the {@link QueryBuilder#prepare()} which supports custom SELECT queries. This should be in turn + * passed to the {@link Dao#query(PreparedQuery)} or {@link Dao#iterator(PreparedQuery)} methods. + * + * @param + * The class that the code will be operating on. + * @author graywatson + */ +public interface PreparedQuery extends PreparedStmt { +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedStmt.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedStmt.java new file mode 100644 index 0000000..b682de5 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedStmt.java @@ -0,0 +1,55 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; + +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.DatabaseConnection; + +/** + * Parent interface for the {@link PreparedQuery}, {@link PreparedUpdate}, and {@link PreparedDelete} interfaces. + */ +public interface PreparedStmt extends GenericRowMapper { + + /** + * Create and return the associated compiled statement. You must call {@link CompiledStatement#close()} after you + * are done with the statement so any database connections can be freed. + */ + public CompiledStatement compile(DatabaseConnection databaseConnection, StatementType type) throws SQLException; + + /** + * Like compile(DatabaseConnection, StatementType) but allows to specify the result flags. + * + * @param resultFlags + * Set to -1 for default. + */ + public CompiledStatement compile(DatabaseConnection databaseConnection, StatementType type, int resultFlags) + throws SQLException; + + /** + * Return the associated SQL statement string for logging purposes. + */ + public String getStatement() throws SQLException; + + /** + * Return the type of the statement for internal consistency checking. + */ + public StatementType getType(); + + /** + * If any argument holder's have been set in this prepared statement then this is a convenience method to be able to + * set them. + * + *

+ * NOTE This method is for folks who know what they are doing. Unfortunately the index of the argument holder + * is dependent on how the query was built which for complex queries may be difficult to determine. Also, certain + * field types (such as a Date) allocate an argument internally so you will need to take this into account. + *

+ * + * @param index + * The index of the holder you are going to set, 0 based. See NOTE above. + * @param value + * Object to set in the argument holder. + */ + public void setArgumentHolderValue(int index, Object value) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedUpdate.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedUpdate.java new file mode 100644 index 0000000..e9c2b58 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/PreparedUpdate.java @@ -0,0 +1,14 @@ +package com.j256.ormlite.stmt; + +import com.j256.ormlite.dao.Dao; + +/** + * Interface returned by the {@link UpdateBuilder#prepare()} which supports custom UPDATE statements. This should be in + * turn passed to the {@link Dao#update(PreparedUpdate)} method. + * + * @param + * The class that the code will be operating on. + * @author graywatson + */ +public interface PreparedUpdate extends PreparedStmt { +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/QueryBuilder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/QueryBuilder.java new file mode 100644 index 0000000..0db6740 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/QueryBuilder.java @@ -0,0 +1,964 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.j256.ormlite.dao.CloseableIterator; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.GenericRawResults; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.ForeignCollectionField; +import com.j256.ormlite.stmt.query.ColumnNameOrRawSql; +import com.j256.ormlite.stmt.query.OrderBy; +import com.j256.ormlite.table.TableInfo; + +/** + * Assists in building sql query (SELECT) statements for a particular table in a particular database. + * + *

+ * Here is a good tutorial of SQL commands. + *

+ * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public class QueryBuilder extends StatementBuilder { + + private final FieldType idField; + private FieldType[] resultFieldTypes; + + private boolean distinct; + private boolean selectIdColumn; + private List selectList; + private List orderByList; + private List groupByList; + private boolean isInnerQuery; + private String countOfQuery; + private String having; + private Long limit; + private Long offset; + private List joinList; + + // NOTE: anything added here should be added to the clear() method below + + public QueryBuilder(DatabaseType databaseType, TableInfo tableInfo, Dao dao) { + super(databaseType, tableInfo, dao, StatementType.SELECT); + this.idField = tableInfo.getIdField(); + this.selectIdColumn = (idField != null); + } + + /** + * This is used by the internal call structure to note when a query builder is being used as an inner query. This is + * necessary because by default, we add in the ID column on every query. When you are returning a data item, its ID + * field _must_ be set otherwise you can't do a refresh() or update(). But internal queries must have 1 select + * column set so we can't add the ID. + */ + void enableInnerQuery() { + this.isInnerQuery = true; + } + + /** + * Return the number of selected columns in the query. + */ + int getSelectColumnCount() { + if (countOfQuery != null) { + return 1; + } else if (selectList == null) { + return 0; + } else { + return selectList.size(); + } + } + + /** + * Return the selected columns in the query or an empty list if none were specified. + */ + String getSelectColumnsAsString() { + if (countOfQuery != null) { + return "COUNT(" + countOfQuery + ")"; + } else if (selectList == null) { + return ""; + } else { + return selectList.toString(); + } + } + + /** + * Build and return a prepared query that can be used by {@link Dao#query(PreparedQuery)} or + * {@link Dao#iterator(PreparedQuery)} methods. If you change the where or make other calls you will need to re-call + * this method to re-prepare the statement for execution. + */ + public PreparedQuery prepare() throws SQLException { + return super.prepareStatement(limit); + } + + /** + * Add columns to be returned by the SELECT query. If no columns are selected then all columns are returned by + * default. For classes with id columns, the id column is added to the select list automagically. This can be called + * multiple times to add more columns to select. + * + *

+ * WARNING: If you specify any columns to return, then any foreign-collection fields will be returned as null + * unless their {@link ForeignCollectionField#columnName()} is also in the list. + *

+ */ + public QueryBuilder selectColumns(String... columns) { + for (String column : columns) { + addSelectColumnToList(column); + } + return this; + } + + /** + * Same as {@link #selectColumns(String...)} except the columns are specified as an iterable -- probably will be a + * {@link Collection}. This can be called multiple times to add more columns to select. + */ + public QueryBuilder selectColumns(Iterable columns) { + for (String column : columns) { + addSelectColumnToList(column); + } + return this; + } + + /** + * Add raw columns or aggregate functions (COUNT, MAX, ...) to the query. This will turn the query into something + * only suitable for the {@link Dao#queryRaw(String, String...)} type of statement. This can be called multiple + * times to add more columns to select. + */ + public QueryBuilder selectRaw(String... columns) { + for (String column : columns) { + addSelectToList(ColumnNameOrRawSql.withRawSql(column)); + } + return this; + } + + /** + * Add "GROUP BY" clause to the SQL query statement. This can be called multiple times to add additional "GROUP BY" + * clauses. + * + *

+ * NOTE: Use of this means that the resulting objects may not have a valid ID column value so cannot be deleted or + * updated. + *

+ */ + public QueryBuilder groupBy(String columnName) { + FieldType fieldType = verifyColumnName(columnName); + if (fieldType.isForeignCollection()) { + throw new IllegalArgumentException("Can't groupBy foreign colletion field: " + columnName); + } + addGroupBy(ColumnNameOrRawSql.withColumnName(columnName)); + return this; + } + + /** + * Add a raw SQL "GROUP BY" clause to the SQL query statement. This should not include the "GROUP BY". + */ + public QueryBuilder groupByRaw(String rawSql) { + addGroupBy(ColumnNameOrRawSql.withRawSql(rawSql)); + return this; + } + + /** + * Add "ORDER BY" clause to the SQL query statement. This can be called multiple times to add additional "ORDER BY" + * clauses. Ones earlier are applied first. + */ + public QueryBuilder orderBy(String columnName, boolean ascending) { + FieldType fieldType = verifyColumnName(columnName); + if (fieldType.isForeignCollection()) { + throw new IllegalArgumentException("Can't orderBy foreign colletion field: " + columnName); + } + addOrderBy(new OrderBy(columnName, ascending)); + return this; + } + + /** + * Add raw SQL "ORDER BY" clause to the SQL query statement. + * + * @param rawSql + * The raw SQL order by clause. This should not include the "ORDER BY". + */ + public QueryBuilder orderByRaw(String rawSql) { + addOrderBy(new OrderBy(rawSql, (ArgumentHolder[]) null)); + return this; + } + + /** + * Add raw SQL "ORDER BY" clause to the SQL query statement. + * + * @param rawSql + * The raw SQL order by clause. This should not include the "ORDER BY". + * @param args + * Optional arguments that correspond to any ? specified in the rawSql. Each of the arguments must have + * the sql-type set. + */ + public QueryBuilder orderByRaw(String rawSql, ArgumentHolder... args) { + addOrderBy(new OrderBy(rawSql, args)); + return this; + } + + /** + * Add "DISTINCT" clause to the SQL query statement. + * + *

+ * NOTE: Use of this means that the resulting objects may not have a valid ID column value so cannot be deleted or + * updated. + *

+ */ + public QueryBuilder distinct() { + distinct = true; + selectIdColumn = false; + return this; + } + + /** + * @deprecated Should use {@link #limit(Long)} + */ + @Deprecated + public QueryBuilder limit(int maxRows) { + return limit((long) maxRows); + } + + /** + * Limit the output to maxRows maximum number of rows. Set to null for no limit (the default). + */ + public QueryBuilder limit(Long maxRows) { + limit = maxRows; + return this; + } + + /** + * @deprecated Should use {@link #offset(Long)} + */ + @Deprecated + public QueryBuilder offset(int startRow) throws SQLException { + return offset((long) startRow); + } + + /** + * Start the output at this row number. Set to null for no offset (the default). If you are paging through a table, + * you should consider using the {@link Dao#iterator()} method instead which handles paging with a database cursor. + * Otherwise, if you are paging you probably want to specify a {@link #orderBy(String, boolean)}. + * + *

+ * NOTE: This is not supported for all databases. Also, for some databases, the limit _must_ also be + * specified since the offset is an argument of the limit. + *

+ */ + public QueryBuilder offset(Long startRow) throws SQLException { + if (databaseType.isOffsetSqlSupported()) { + offset = startRow; + return this; + } else { + throw new SQLException("Offset is not supported by this database"); + } + } + + /** + * Set whether or not we should only return the count of the results. This query can then be used by + * {@link Dao#countOf(PreparedQuery)}. + * + * To get the count-of directly, use {@link #countOf()}. + */ + public QueryBuilder setCountOf(boolean countOf) { + return setCountOf("*"); + } + + /** + * Set the field that we are counting from the database. For example, you can + * {@code qb.setCountOf("DISTINCT(fieldname)")}. This query can then be used by {@link Dao#countOf(PreparedQuery)}. + * + * To get the count-of directly, use {@link #countOf(String)}. + */ + public QueryBuilder setCountOf(String countOfQuery) { + this.countOfQuery = countOfQuery; + return this; + } + + /** + * Add raw SQL "HAVING" clause to the SQL query statement. This should not include the "HAVING" string. + */ + public QueryBuilder having(String having) { + this.having = having; + return this; + } + + /** + * Join with another query builder. This will add into the SQL something close to " INNER JOIN other-table ...". + * Either the object associated with the current QueryBuilder or the argument QueryBuilder must have a foreign field + * of the other one. An exception will be thrown otherwise. + * + *

+ * NOTE: This will do combine the WHERE statement of the two query builders with a SQL "AND". See + * {@link #joinOr(QueryBuilder)}. + *

+ */ + public QueryBuilder join(QueryBuilder joinedQueryBuilder) throws SQLException { + addJoinInfo(JoinType.INNER, null, null, joinedQueryBuilder, JoinWhereOperation.AND); + return this; + } + + /** + * Like {@link #join(QueryBuilder)} but allows you to specify the join type and the operation used to combine the + * WHERE statements. + */ + public QueryBuilder join(QueryBuilder joinedQueryBuilder, JoinType type, JoinWhereOperation operation) + throws SQLException { + addJoinInfo(type, null, null, joinedQueryBuilder, operation); + return this; + } + + /** + * Like {@link #join(QueryBuilder)} but this combines the WHERE statements of two query builders with a SQL "OR". + */ + public QueryBuilder joinOr(QueryBuilder joinedQueryBuilder) throws SQLException { + addJoinInfo(JoinType.INNER, null, null, joinedQueryBuilder, JoinWhereOperation.OR); + return this; + } + + /** + * Similar to {@link #join(QueryBuilder)} but it will use "LEFT JOIN" instead. + * + * See: LEFT JOIN SQL docs + * + *

+ * NOTE: RIGHT and FULL JOIN SQL commands are not supported because we are only returning objects from the + * "left" table. + *

+ * + *

+ * NOTE: This will do combine the WHERE statement of the two query builders with a SQL "AND". See + * {@link #leftJoinOr(QueryBuilder)}. + *

+ */ + public QueryBuilder leftJoin(QueryBuilder joinedQueryBuilder) throws SQLException { + addJoinInfo(JoinType.LEFT, null, null, joinedQueryBuilder, JoinWhereOperation.AND); + return this; + } + + /** + * Like {@link #leftJoin(QueryBuilder)} but this combines the WHERE statements of two query builders with a SQL + * "OR". + */ + public QueryBuilder leftJoinOr(QueryBuilder joinedQueryBuilder) throws SQLException { + addJoinInfo(JoinType.LEFT, null, null, joinedQueryBuilder, JoinWhereOperation.OR); + return this; + } + + /** + * Similar to {@link #join(QueryBuilder)} but this allows you to link two tables that share a field of the same + * type. So even if there is _not_ a foreign-object relationship between the tables, you can JOIN them. This will + * add into the SQL something close to " INNER JOIN other-table ...". + */ + public QueryBuilder join(String localColumnName, String joinedColumnName, + QueryBuilder joinedQueryBuilder) throws SQLException { + addJoinInfo(JoinType.INNER, localColumnName, joinedColumnName, joinedQueryBuilder, JoinWhereOperation.AND); + return this; + } + + /** + * Similar to {@link #join(QueryBuilder, JoinType, JoinWhereOperation)} but this allows you to link two tables that + * share a field of the same type. + */ + public QueryBuilder join(String localColumnName, String joinedColumnName, + QueryBuilder joinedQueryBuilder, JoinType type, JoinWhereOperation operation) throws SQLException { + addJoinInfo(type, localColumnName, joinedColumnName, joinedQueryBuilder, operation); + return this; + } + + /** + * A short cut to {@link Dao#query(PreparedQuery)}. + */ + public List query() throws SQLException { + return dao.query(prepare()); + } + + /** + * A short cut to {@link Dao#queryRaw(String, String...)}. + */ + public GenericRawResults queryRaw() throws SQLException { + return dao.queryRaw(prepareStatementString()); + } + + /** + * A short cut to {@link Dao#queryForFirst(PreparedQuery)}. + */ + public T queryForFirst() throws SQLException { + return dao.queryForFirst(prepare()); + } + + /** + * A short cut to {@link Dao#queryRaw(String, String...)} and {@link GenericRawResults#getFirstResult()}. + */ + public String[] queryRawFirst() throws SQLException { + return dao.queryRaw(prepareStatementString()).getFirstResult(); + } + + /** + * A short cut to {@link Dao#iterator(PreparedQuery)}. + */ + public CloseableIterator iterator() throws SQLException { + return dao.iterator(prepare()); + } + + /** + * Returns the count of the number of rows in the table. This uses {@link #setCountOf(boolean)} to true and then + * calls {@link Dao#countOf(PreparedQuery)}. It restores the previous count-of value before returning. + */ + public long countOf() throws SQLException { + String countOfQuerySave = this.countOfQuery; + try { + setCountOf(true); + return dao.countOf(prepare()); + } finally { + setCountOf(countOfQuerySave); + } + } + + /** + * Returns the count of the number of rows that match a field so you can do + * {@code qb.countOf("DISTINCT(fieldname)")}. This uses {@link #setCountOf(String)} and then calls + * {@link Dao#countOf(PreparedQuery)}. It restores the previous count-of value before returning. + */ + public long countOf(String countOfQuery) throws SQLException { + String countOfQuerySave = this.countOfQuery; + try { + setCountOf(countOfQuery); + return dao.countOf(prepare()); + } finally { + setCountOf(countOfQuerySave); + } + } + + /** + * @deprecated Renamed to be {@link #reset()}. + */ + @Deprecated + @Override + public void clear() { + reset(); + } + + @Override + public void reset() { + super.reset(); + distinct = false; + selectIdColumn = (idField != null); + if (selectList != null) { + selectList.clear(); + selectList = null; + } + if (orderByList != null) { + orderByList.clear(); + orderByList = null; + } + if (groupByList != null) { + groupByList.clear(); + groupByList = null; + } + isInnerQuery = false; + countOfQuery = null; + having = null; + limit = null; + offset = null; + if (joinList != null) { + // help gc + joinList.clear(); + joinList = null; + } + addTableName = false; + } + + @Override + protected void appendStatementStart(StringBuilder sb, List argList) { + if (joinList == null) { + setAddTableName(false); + } else { + setAddTableName(true); + } + sb.append("SELECT "); + if (databaseType.isLimitAfterSelect()) { + appendLimit(sb); + } + if (distinct) { + sb.append("DISTINCT "); + } + if (countOfQuery == null) { + // type set in appendSelects depending on raw or not + appendSelects(sb); + } else { + type = StatementType.SELECT_LONG; + sb.append("COUNT(").append(countOfQuery).append(") "); + } + sb.append("FROM "); + databaseType.appendEscapedEntityName(sb, tableName); + sb.append(' '); + if (joinList != null) { + appendJoinSql(sb); + } + } + + @Override + protected FieldType[] getResultFieldTypes() { + return resultFieldTypes; + } + + @Override + protected boolean appendWhereStatement(StringBuilder sb, List argList, WhereOperation operation) + throws SQLException { + boolean first = (operation == WhereOperation.FIRST); + if (this.where != null) { + first = super.appendWhereStatement(sb, argList, operation); + } + if (joinList != null) { + for (JoinInfo joinInfo : joinList) { + if (first) { + operation = WhereOperation.FIRST; + } else { + operation = joinInfo.operation.whereOperation; + } + first = joinInfo.queryBuilder.appendWhereStatement(sb, argList, operation); + } + } + return first; + } + + @Override + protected void appendStatementEnd(StringBuilder sb, List argList) throws SQLException { + // 'group by' comes before 'order by' + appendGroupBys(sb); + appendHaving(sb); + appendOrderBys(sb, argList); + if (!databaseType.isLimitAfterSelect()) { + appendLimit(sb); + } + appendOffset(sb); + // clear the add-table name flag so we can reuse the builder + setAddTableName(false); + } + + @Override + protected boolean shouldPrependTableNameToColumns() { + return joinList != null; + } + + private void addOrderBy(OrderBy orderBy) { + if (orderByList == null) { + orderByList = new ArrayList(); + } + orderByList.add(orderBy); + } + + private void addGroupBy(ColumnNameOrRawSql groupBy) { + if (groupByList == null) { + groupByList = new ArrayList(); + } + groupByList.add(groupBy); + selectIdColumn = false; + } + + private void setAddTableName(boolean addTableName) { + this.addTableName = addTableName; + if (joinList != null) { + for (JoinInfo joinInfo : joinList) { + joinInfo.queryBuilder.setAddTableName(addTableName); + } + } + } + + /** + * Add join info to the query. This can be called multiple times to join with more than one table. + */ + private void addJoinInfo(JoinType type, String localColumnName, String joinedColumnName, + QueryBuilder joinedQueryBuilder, JoinWhereOperation operation) throws SQLException { + JoinInfo joinInfo = new JoinInfo(type, joinedQueryBuilder, operation); + if (localColumnName == null) { + matchJoinedFields(joinInfo, joinedQueryBuilder); + } else { + matchJoinedFieldsByName(joinInfo, localColumnName, joinedColumnName, joinedQueryBuilder); + } + if (joinList == null) { + joinList = new ArrayList(); + } + joinList.add(joinInfo); + } + + /** + * Match up our joined fields so we can throw a nice exception immediately if you can't join with this type. + */ + private void matchJoinedFieldsByName(JoinInfo joinInfo, String localColumnName, String joinedColumnName, + QueryBuilder joinedQueryBuilder) throws SQLException { + joinInfo.localField = tableInfo.getFieldTypeByColumnName(localColumnName); + if (joinInfo.localField == null) { + throw new SQLException("Could not find field in " + tableInfo.getDataClass() + " that has column-name '" + + localColumnName + "'"); + } + joinInfo.remoteField = joinedQueryBuilder.tableInfo.getFieldTypeByColumnName(joinedColumnName); + if (joinInfo.remoteField == null) { + throw new SQLException("Could not find field in " + joinedQueryBuilder.tableInfo.getDataClass() + + " that has column-name '" + joinedColumnName + "'"); + } + } + + /** + * Match up our joined fields so we can throw a nice exception immediately if you can't join with this type. + */ + private void matchJoinedFields(JoinInfo joinInfo, QueryBuilder joinedQueryBuilder) throws SQLException { + for (FieldType fieldType : tableInfo.getFieldTypes()) { + // if this is a foreign field and its foreign-id field is the same as the other's id + FieldType foreignIdField = fieldType.getForeignIdField(); + if (fieldType.isForeign() && foreignIdField.equals(joinedQueryBuilder.tableInfo.getIdField())) { + joinInfo.localField = fieldType; + joinInfo.remoteField = foreignIdField; + return; + } + } + // if this other field is a foreign field and its foreign-id field is our id + for (FieldType fieldType : joinedQueryBuilder.tableInfo.getFieldTypes()) { + if (fieldType.isForeign() && fieldType.getForeignIdField().equals(idField)) { + joinInfo.localField = idField; + joinInfo.remoteField = fieldType; + return; + } + } + + throw new SQLException("Could not find a foreign " + tableInfo.getDataClass() + " field in " + + joinedQueryBuilder.tableInfo.getDataClass() + " or vice versa"); + } + + private void addSelectColumnToList(String columnName) { + verifyColumnName(columnName); + addSelectToList(ColumnNameOrRawSql.withColumnName(columnName)); + } + + private void addSelectToList(ColumnNameOrRawSql select) { + if (selectList == null) { + selectList = new ArrayList(); + } + selectList.add(select); + } + + private void appendJoinSql(StringBuilder sb) { + for (JoinInfo joinInfo : joinList) { + sb.append(joinInfo.type.sql).append(" JOIN "); + databaseType.appendEscapedEntityName(sb, joinInfo.queryBuilder.tableName); + sb.append(" ON "); + databaseType.appendEscapedEntityName(sb, tableName); + sb.append('.'); + databaseType.appendEscapedEntityName(sb, joinInfo.localField.getColumnName()); + sb.append(" = "); + databaseType.appendEscapedEntityName(sb, joinInfo.queryBuilder.tableName); + sb.append('.'); + databaseType.appendEscapedEntityName(sb, joinInfo.remoteField.getColumnName()); + sb.append(' '); + // keep on going down if multiple JOIN layers + if (joinInfo.queryBuilder.joinList != null) { + joinInfo.queryBuilder.appendJoinSql(sb); + } + } + } + + private void appendSelects(StringBuilder sb) { + // the default + type = StatementType.SELECT; + + // if no columns were specified then * is the default + if (selectList == null) { + if (addTableName) { + databaseType.appendEscapedEntityName(sb, tableName); + sb.append('.'); + } + sb.append("* "); + resultFieldTypes = tableInfo.getFieldTypes(); + return; + } + + boolean first = true; + boolean hasId; + if (isInnerQuery) { + hasId = true; + } else { + hasId = false; + } + List fieldTypeList = new ArrayList(selectList.size() + 1); + for (ColumnNameOrRawSql select : selectList) { + if (select.getRawSql() != null) { + // if any are raw-sql then that's our type + type = StatementType.SELECT_RAW; + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(select.getRawSql()); + continue; + } + FieldType fieldType = tableInfo.getFieldTypeByColumnName(select.getColumnName()); + /* + * If this is a foreign-collection then we add it to our field-list but _not_ to the select list because + * foreign collections don't have a column in the database. + */ + if (fieldType.isForeignCollection()) { + fieldTypeList.add(fieldType); + continue; + } + if (first) { + first = false; + } else { + sb.append(", "); + } + appendFieldColumnName(sb, fieldType, fieldTypeList); + if (fieldType == idField) { + hasId = true; + } + } + + if (type != StatementType.SELECT_RAW) { + // we have to add the idField even if it isn't in the columnNameSet + if (!hasId && selectIdColumn) { + if (!first) { + sb.append(','); + } + appendFieldColumnName(sb, idField, fieldTypeList); + } + + resultFieldTypes = fieldTypeList.toArray(new FieldType[fieldTypeList.size()]); + } + sb.append(' '); + } + + private void appendFieldColumnName(StringBuilder sb, FieldType fieldType, List fieldTypeList) { + appendColumnName(sb, fieldType.getColumnName()); + if (fieldTypeList != null) { + fieldTypeList.add(fieldType); + } + } + + private void appendLimit(StringBuilder sb) { + if (limit != null && databaseType.isLimitSqlSupported()) { + databaseType.appendLimitValue(sb, limit, offset); + } + } + + private void appendOffset(StringBuilder sb) throws SQLException { + if (offset == null) { + return; + } + if (databaseType.isOffsetLimitArgument()) { + if (limit == null) { + throw new SQLException("If the offset is specified, limit must also be specified with this database"); + } + } else { + databaseType.appendOffsetValue(sb, offset); + } + } + + private void appendGroupBys(StringBuilder sb) { + boolean first = true; + if (hasGroupStuff()) { + appendGroupBys(sb, first); + first = false; + } + /* + * NOTE: this may not be legal and doesn't seem to work with some database types but we'll check this out + * anyway. + */ + if (joinList != null) { + for (JoinInfo joinInfo : joinList) { + if (joinInfo.queryBuilder != null && joinInfo.queryBuilder.hasGroupStuff()) { + joinInfo.queryBuilder.appendGroupBys(sb, first); + first = false; + } + } + } + } + + private boolean hasGroupStuff() { + return (groupByList != null && !groupByList.isEmpty()); + } + + private void appendGroupBys(StringBuilder sb, boolean first) { + if (first) { + sb.append("GROUP BY "); + } + for (ColumnNameOrRawSql groupBy : groupByList) { + if (first) { + first = false; + } else { + sb.append(','); + } + if (groupBy.getRawSql() == null) { + appendColumnName(sb, groupBy.getColumnName()); + } else { + sb.append(groupBy.getRawSql()); + } + } + sb.append(' '); + } + + private void appendOrderBys(StringBuilder sb, List argList) { + boolean first = true; + if (hasOrderStuff()) { + appendOrderBys(sb, first, argList); + first = false; + } + /* + * NOTE: this may not be necessary since the inner results aren't at all returned but we'll leave this code here + * anyway. + */ + if (joinList != null) { + for (JoinInfo joinInfo : joinList) { + if (joinInfo.queryBuilder != null && joinInfo.queryBuilder.hasOrderStuff()) { + joinInfo.queryBuilder.appendOrderBys(sb, first, argList); + first = false; + } + } + } + } + + private boolean hasOrderStuff() { + return (orderByList != null && !orderByList.isEmpty()); + } + + private void appendOrderBys(StringBuilder sb, boolean first, List argList) { + if (first) { + sb.append("ORDER BY "); + } + for (OrderBy orderBy : orderByList) { + if (first) { + first = false; + } else { + sb.append(','); + } + if (orderBy.getRawSql() == null) { + appendColumnName(sb, orderBy.getColumnName()); + if (orderBy.isAscending()) { + // here for documentation purposes, ASC is the default + // sb.append(" ASC"); + } else { + sb.append(" DESC"); + } + } else { + sb.append(orderBy.getRawSql()); + if (orderBy.getOrderByArgs() != null) { + for (ArgumentHolder arg : orderBy.getOrderByArgs()) { + argList.add(arg); + } + } + } + } + sb.append(' '); + } + + private void appendColumnName(StringBuilder sb, String columnName) { + if (addTableName) { + databaseType.appendEscapedEntityName(sb, tableName); + sb.append('.'); + } + databaseType.appendEscapedEntityName(sb, columnName); + } + + private void appendHaving(StringBuilder sb) { + if (having != null) { + sb.append("HAVING ").append(having).append(' '); + } + } + + /** + * Encapsulates our join information. + */ + private class JoinInfo { + final JoinType type; + final QueryBuilder queryBuilder; + FieldType localField; + FieldType remoteField; + JoinWhereOperation operation; + + public JoinInfo(JoinType type, QueryBuilder queryBuilder, JoinWhereOperation operation) { + this.type = type; + this.queryBuilder = queryBuilder; + this.operation = operation; + } + } + + /** + * Internal class used to expose methods to internal classes but through a wrapper instead of a builder. + */ + public static class InternalQueryBuilderWrapper { + + private final QueryBuilder queryBuilder; + + InternalQueryBuilderWrapper(QueryBuilder queryBuilder) { + this.queryBuilder = queryBuilder; + } + + public void appendStatementString(StringBuilder sb, List argList) throws SQLException { + queryBuilder.appendStatementString(sb, argList); + } + + public FieldType[] getResultFieldTypes() { + return queryBuilder.getResultFieldTypes(); + } + } + + /** + * Type of the JOIN that we are adding. + * + *

+ * NOTE: RIGHT and FULL JOIN SQL commands are not supported because we are only returning objects from the + * "left" table. + */ + public enum JoinType { + /** + * The most common type of join. "An SQL INNER JOIN return all rows from multiple tables where the join + * condition is met." + * + *

+ * See SQL JOIN + *

+ */ + INNER("INNER"), + /** + * "The LEFT JOIN keyword returns all rows from the left table (table1), with the matching rows in the right + * table (table2). The result is NULL in the right side when there is no match." + * + *

+ * See: LEFT JOIN SQL docs + *

+ */ + LEFT("LEFT"), + // end + ; + + private String sql; + + private JoinType(String sql) { + this.sql = sql; + } + } + + /** + * When we are combining WHERE statements from the two joined query-builders, this determines the operator to use to + * do so. + */ + public enum JoinWhereOperation { + /** combine the two WHERE parts of the JOINed queries with an AND */ + AND(WhereOperation.AND), + /** combine the two WHERE parts of the JOINed queries with an OR */ + OR(WhereOperation.OR), + // end + ; + + private WhereOperation whereOperation; + + private JoinWhereOperation(WhereOperation whereOperation) { + this.whereOperation = whereOperation; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawResultsImpl.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawResultsImpl.java new file mode 100644 index 0000000..5dcca33 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawResultsImpl.java @@ -0,0 +1,85 @@ +package com.j256.ormlite.stmt; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.dao.CloseableIterator; +import com.j256.ormlite.dao.GenericRawResults; +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; + +/** + * Handler for our raw results objects which does the conversion for various different results: String[], Object[], and + * user defined T. + * + * @author graywatson + */ +public class RawResultsImpl implements GenericRawResults { + + private SelectIterator iterator; + private final String[] columnNames; + + public RawResultsImpl(ConnectionSource connectionSource, DatabaseConnection connection, String query, + Class clazz, CompiledStatement compiledStmt, GenericRowMapper rowMapper, ObjectCache objectCache) + throws SQLException { + iterator = + new SelectIterator(clazz, null, rowMapper, connectionSource, connection, compiledStmt, query, + objectCache); + /* + * NOTE: we _have_ to get these here before the results object is closed if there are no results + */ + this.columnNames = iterator.getRawResults().getColumnNames(); + } + + public int getNumberColumns() { + return columnNames.length; + } + + public String[] getColumnNames() { + return columnNames; + } + + public List getResults() throws SQLException { + List results = new ArrayList(); + try { + while (iterator.hasNext()) { + results.add(iterator.next()); + } + return results; + } finally { + IOUtils.closeThrowSqlException(this, "raw results iterator"); + } + } + + public T getFirstResult() throws SQLException { + try { + if (iterator.hasNextThrow()) { + return iterator.nextThrow(); + } else { + return null; + } + } finally { + IOUtils.closeThrowSqlException(this, "raw results iterator"); + } + } + + public CloseableIterator iterator() { + return iterator; + } + + public CloseableIterator closeableIterator() { + return iterator; + } + + public void close() throws IOException { + if (iterator != null) { + iterator.close(); + iterator = null; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawRowMapperImpl.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawRowMapperImpl.java new file mode 100644 index 0000000..4acf683 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/RawRowMapperImpl.java @@ -0,0 +1,39 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.RawRowMapper; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.table.TableInfo; + +/** + * Default row mapper when you are using the {@link Dao#queryRaw(String, RawRowMapper, String...)}. + * + * @author graywatson + */ +public class RawRowMapperImpl implements RawRowMapper { + + private final TableInfo tableInfo; + + public RawRowMapperImpl(TableInfo tableInfo) { + this.tableInfo = tableInfo; + } + + public T mapRow(String[] columnNames, String[] resultColumns) throws SQLException { + // create our object + T rowObj = tableInfo.createObject(); + for (int i = 0; i < columnNames.length; i++) { + // sanity check, prolly will never happen but let's be careful out there + if (i >= resultColumns.length) { + continue; + } + // run through and convert each field + FieldType fieldType = tableInfo.getFieldTypeByColumnName(columnNames[i]); + Object fieldObj = fieldType.convertStringToJavaField(resultColumns[i], i); + // assign it to the row object + fieldType.assignField(rowObj, fieldObj, false, null); + } + return rowObj; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectArg.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectArg.java new file mode 100644 index 0000000..66c6f42 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectArg.java @@ -0,0 +1,104 @@ +package com.j256.ormlite.stmt; + +import com.j256.ormlite.field.SqlType; + +/** + * An argument to a select SQL statement. After the query is constructed, the caller can set the value on this argument + * and run the query. Then the argument can be set again and the query re-executed. This is equivalent in SQL to a ? + * argument. + * + *

+ * NOTE: If the argument has not been set by the time the query is executed, an exception will be thrown. + *

+ * + *

+ * NOTE: For protections sake, the object cannot be reused with different column names. + *

+ * + * @author graywatson + */ +public class SelectArg extends BaseArgumentHolder { + + private boolean hasBeenSet = false; + private Object value = null; + + /** + * Constructor for when the value will be set later with {@link #setValue(Object)}. + */ + public SelectArg() { + super(); + // value set later + } + + /** + * This constructor is only necessary if you are using the {@link Where#raw(String, ArgumentHolder...)} and similar + * methods. + * + * @param columnName + * Name of the column this argument corresponds to. + * @param value + * Value for the select-arg if know at time of construction. Otherwise call {@link #setValue(Object)} + * later. + */ + public SelectArg(String columnName, Object value) { + super(columnName); + setValue(value); + } + + /** + * This constructor is only necessary if you are using the {@link Where#raw(String, ArgumentHolder...)} and similar + * methods. + * + * @param sqlType + * Type of the column that this argument corresponds to. Only necessary if you are using the + * {@link Where#raw(String, ArgumentHolder...)} and similar methods. + * @param value + * Value for the select-arg if know at time of construction. Otherwise call {@link #setValue(Object)} + * later. + */ + public SelectArg(SqlType sqlType, Object value) { + super(sqlType); + setValue(value); + } + + /** + * This constructor is only necessary if you are using the {@link Where#raw(String, ArgumentHolder...)} and similar + * methods. + * + * @param sqlType + * Type of the column that this argument corresponds to. Only necessary if you are using the + * {@link Where#raw(String, ArgumentHolder...)} and similar methods. + */ + public SelectArg(SqlType sqlType) { + super(sqlType); + } + + /** + * Constructor for when the value is known at time of construction. You can instead use the {@link #SelectArg()} + * empty constructor and set the value later with {@link #setValue(Object)}. + * + *

+ * WARNING, This constructor sets the _value_ not the column-name. To set the column-name only, use the + * {@link #SelectArg(String, Object)} and pass a null as the value. + *

+ */ + public SelectArg(Object value) { + setValue(value); + } + + @Override + protected Object getValue() { + return value; + } + + @Override + public void setValue(Object value) { + this.hasBeenSet = true; + this.value = value; + } + + @Override + protected boolean isValueSet() { + return hasBeenSet; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectIterator.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectIterator.java new file mode 100644 index 0000000..b99e610 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/SelectIterator.java @@ -0,0 +1,277 @@ +package com.j256.ormlite.stmt; + +import java.io.IOException; +import java.sql.SQLException; + +import com.j256.ormlite.dao.CloseableIterator; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Internal iterator so we can page through the class. This is used by the {@link Dao#iterator} methods. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public class SelectIterator implements CloseableIterator { + + private final static Logger logger = LoggerFactory.getLogger(SelectIterator.class); + + private final Class dataClass; + private final Dao classDao; + private final ConnectionSource connectionSource; + private final DatabaseConnection connection; + private final CompiledStatement compiledStmt; + private final DatabaseResults results; + private final GenericRowMapper rowMapper; + private final String statement; + private boolean first = true; + private boolean closed; + private boolean alreadyMoved; + private T last; + private int rowC; + + /** + * If the statement parameter is null then this won't log information + */ + public SelectIterator(Class dataClass, Dao classDao, GenericRowMapper rowMapper, + ConnectionSource connectionSource, DatabaseConnection connection, CompiledStatement compiledStmt, + String statement, ObjectCache objectCache) throws SQLException { + this.dataClass = dataClass; + this.classDao = classDao; + this.rowMapper = rowMapper; + this.connectionSource = connectionSource; + this.connection = connection; + this.compiledStmt = compiledStmt; + this.results = compiledStmt.runQuery(objectCache); + this.statement = statement; + if (statement != null) { + logger.debug("starting iterator @{} for '{}'", hashCode(), statement); + } + } + + /** + * Returns whether or not there are any remaining objects in the table. Can be called before next(). + * + * @throws SQLException + * If there was a problem getting more results via SQL. + */ + public boolean hasNextThrow() throws SQLException { + if (closed) { + return false; + } + if (alreadyMoved) { + // we do this so multiple hasNext() calls can be made, result would be true or closed is true + return true; + } + boolean result; + if (first) { + first = false; + result = results.first(); + } else { + result = results.next(); + } + if (!result) { + IOUtils.closeThrowSqlException(this, "iterator"); + } + alreadyMoved = true; + return result; + } + + /** + * Returns whether or not there are any remaining objects in the table. Can be called before next(). + * + * @throws IllegalStateException + * If there was a problem getting more results via SQL. + */ + public boolean hasNext() { + try { + return hasNextThrow(); + } catch (SQLException e) { + last = null; + closeQuietly(); + // unfortunately, can't propagate back the SQLException + throw new IllegalStateException("Errors getting more results of " + dataClass, e); + } + } + + public T first() throws SQLException { + if (closed) { + return null; + } + first = false; + if (results.first()) { + return getCurrent(); + } else { + return null; + } + } + + public T previous() throws SQLException { + if (closed) { + return null; + } + first = false; + if (results.previous()) { + return getCurrent(); + } else { + return null; + } + } + + public T current() throws SQLException { + if (closed) { + return null; + } + if (first) { + return first(); + } else { + return getCurrent(); + } + } + + public T nextThrow() throws SQLException { + if (closed) { + return null; + } + if (!alreadyMoved) { + boolean hasResult; + if (first) { + first = false; + hasResult = results.first(); + } else { + hasResult = results.next(); + } + // move forward + if (!hasResult) { + first = false; + return null; + } + } + first = false; + return getCurrent(); + } + + /** + * Returns the next object in the table. + * + * @throws IllegalStateException + * If there was a problem extracting the object from SQL. + */ + public T next() { + SQLException sqlException = null; + try { + T result = nextThrow(); + if (result != null) { + return result; + } + } catch (SQLException e) { + sqlException = e; + } + // we have to throw if there is no next or on a SQLException + last = null; + closeQuietly(); + throw new IllegalStateException("Could not get next result for " + dataClass, sqlException); + } + + public T moveRelative(int offset) throws SQLException { + if (closed) { + return null; + } + first = false; + if (results.moveRelative(offset)) { + return getCurrent(); + } else { + return null; + } + } + + /** + * Removes the last object returned by next() by calling delete on the dao associated with the object. + * + * @throws IllegalStateException + * If there was no previous next() call. + * @throws SQLException + * If the delete failed. + */ + public void removeThrow() throws SQLException { + if (last == null) { + throw new IllegalStateException("No last " + dataClass + + " object to remove. Must be called after a call to next."); + } + if (classDao == null) { + // we may never be able to get here since it should only be null for queryForAll methods + throw new IllegalStateException("Cannot remove " + dataClass + " object because classDao not initialized"); + } + try { + classDao.delete(last); + } finally { + // if we've try to delete it, clear the last marker + last = null; + } + } + + /** + * Removes the last object returned by next() by calling delete on the dao associated with the object. + * + * @throws IllegalStateException + * If there was no previous next() call or if delete() throws a SQLException (set as the cause). + */ + public void remove() { + try { + removeThrow(); + } catch (SQLException e) { + closeQuietly(); + // unfortunately, can't propagate back the SQLException + throw new IllegalStateException("Could not delete " + dataClass + " object " + last, e); + } + } + + public void close() throws IOException { + if (!closed) { + compiledStmt.close(); + closed = true; + last = null; + if (statement != null) { + logger.debug("closed iterator @{} after {} rows", hashCode(), rowC); + } + try { + connectionSource.releaseConnection(connection); + } catch (SQLException e) { + throw new IOException("could not release connection", e); + } + } + } + + public void closeQuietly() { + IOUtils.closeQuietly(this); + } + + public DatabaseResults getRawResults() { + return results; + } + + public void moveToNext() { + last = null; + first = false; + alreadyMoved = false; + } + + private T getCurrent() throws SQLException { + last = rowMapper.mapRow(results); + alreadyMoved = false; + rowC++; + return last; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementBuilder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementBuilder.java new file mode 100644 index 0000000..24d009f --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementBuilder.java @@ -0,0 +1,307 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.stmt.mapped.MappedPreparedStmt; +import com.j256.ormlite.table.TableInfo; + +/** + * Assists in building of SQL statements for a particular table in a particular database. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public abstract class StatementBuilder { + + private static Logger logger = LoggerFactory.getLogger(StatementBuilder.class); + + protected final TableInfo tableInfo; + protected final String tableName; + protected final DatabaseType databaseType; + protected final Dao dao; + protected StatementType type; + protected boolean addTableName; + + protected Where where = null; + // NOTE: anything added here should be added to the clear() method below + + public StatementBuilder(DatabaseType databaseType, TableInfo tableInfo, Dao dao, StatementType type) { + this.databaseType = databaseType; + this.tableInfo = tableInfo; + this.tableName = tableInfo.getTableName(); + this.dao = dao; + this.type = type; + if (!type.isOkForStatementBuilder()) { + throw new IllegalStateException("Building a statement from a " + type + " statement is not allowed"); + } + } + + /** + * Returns a {@link Where} object that should be used to add SQL where clauses to the statement. This will also + * reset the where object so you can use the same query builder with a different where statement. + */ + public Where where() { + where = new Where(tableInfo, this, databaseType); + return where; + } + + /** + * Set the {@link Where} object on the query. This allows someone to use the same Where object on multiple queries. + */ + public void setWhere(Where where) { + this.where = where; + } + + /** + * Prepare our statement for the subclasses. + * + * @param limit + * Limit for queries. Can be null if none. + */ + protected MappedPreparedStmt prepareStatement(Long limit) throws SQLException { + List argList = new ArrayList(); + String statement = buildStatementString(argList); + ArgumentHolder[] selectArgs = argList.toArray(new ArgumentHolder[argList.size()]); + FieldType[] resultFieldTypes = getResultFieldTypes(); + FieldType[] argFieldTypes = new FieldType[argList.size()];; + for (int selectC = 0; selectC < selectArgs.length; selectC++) { + argFieldTypes[selectC] = selectArgs[selectC].getFieldType(); + } + if (!type.isOkForStatementBuilder()) { + throw new IllegalStateException("Building a statement from a " + type + " statement is not allowed"); + } + return new MappedPreparedStmt(tableInfo, statement, argFieldTypes, resultFieldTypes, selectArgs, + (databaseType.isLimitSqlSupported() ? null : limit), type); + } + + /** + * Build and return a string version of the query. If you change the where or make other calls you will need to + * re-call this method to re-prepare the query for execution. + */ + public String prepareStatementString() throws SQLException { + List argList = new ArrayList(); + return buildStatementString(argList); + } + + /** + * Build and return all of the information about the prepared statement. See {@link StatementInfo} for more details. + */ + public StatementInfo prepareStatementInfo() throws SQLException { + List argList = new ArrayList(); + String statement = buildStatementString(argList); + return new StatementInfo(statement, argList); + } + + /** + * @deprecated Renamed to be {@link #reset()}. + */ + @Deprecated + public void clear() { + reset(); + } + + /** + * Clear out all of the statement settings so we can reuse the builder. + */ + public void reset() { + where = null; + } + + protected String buildStatementString(List argList) throws SQLException { + StringBuilder sb = new StringBuilder(128); + appendStatementString(sb, argList); + String statement = sb.toString(); + logger.debug("built statement {}", statement); + return statement; + } + + /** + * Internal method to build a query while tracking various arguments. Users should use the + * {@link #prepareStatementString()} method instead. + * + *

+ * This needs to be protected because of (WARNING: DO NOT MAKE A JAVADOC LINK) InternalQueryBuilder (WARNING: DO NOT + * MAKE A JAVADOC LINK). + *

+ */ + protected void appendStatementString(StringBuilder sb, List argList) throws SQLException { + appendStatementStart(sb, argList); + appendWhereStatement(sb, argList, WhereOperation.FIRST); + appendStatementEnd(sb, argList); + } + + /** + * Append the start of our statement string to the StringBuilder. + */ + protected abstract void appendStatementStart(StringBuilder sb, List argList) throws SQLException; + + /** + * Append the WHERE part of the statement to the StringBuilder. + */ + protected boolean appendWhereStatement(StringBuilder sb, List argList, WhereOperation operation) + throws SQLException { + if (where == null) { + return operation == WhereOperation.FIRST; + } + operation.appendBefore(sb); + where.appendSql((addTableName ? tableName : null), sb, argList); + operation.appendAfter(sb); + return false; + } + + /** + * Append the end of our statement string to the StringBuilder. + */ + protected abstract void appendStatementEnd(StringBuilder sb, List argList) throws SQLException; + + /** + * Return true if we need to prepend table-name to columns. + */ + protected boolean shouldPrependTableNameToColumns() { + return false; + } + + /** + * Get the result array from our statement after the {@link #appendStatementStart(StringBuilder, List)} was called. + * This will be null except for the QueryBuilder. + */ + protected FieldType[] getResultFieldTypes() { + return null; + } + + /** + * Verify the columnName is valid and return its FieldType. + * + * @throws IllegalArgumentException + * if the column name is not valid. + */ + protected FieldType verifyColumnName(String columnName) { + return tableInfo.getFieldTypeByColumnName(columnName); + } + + /** + * Return the type of the statement. + */ + StatementType getType() { + return type; + } + + /** + * Types of statements that we are building. + */ + public static enum StatementType { + /** SQL statement in the form of SELECT ... */ + SELECT(true, true, false, false), + /** SQL statement in the form of SELECT COUNT(*)... or something */ + SELECT_LONG(true, true, false, false), + /** SQL statement in the form of SELECT... with aggregate functions or something */ + SELECT_RAW(true, true, false, false), + /** SQL statement in the form of UPDATE ... */ + UPDATE(true, false, true, false), + /** SQL statement in the form of DELETE ... */ + DELETE(true, false, true, false), + /** SQL statement in the form of CREATE TABLE, ALTER TABLE, or something returning the number of rows affected */ + EXECUTE(false, false, false, true), + // end + ; + + private final boolean okForStatementBuilder; + private final boolean okForQuery; + private final boolean okForUpdate; + private final boolean okForExecute; + + private StatementType(boolean okForStatementBuilder, boolean okForQuery, boolean okForUpdate, + boolean okForExecute) { + this.okForStatementBuilder = okForStatementBuilder; + this.okForQuery = okForQuery; + this.okForUpdate = okForUpdate; + this.okForExecute = okForExecute; + } + + public boolean isOkForStatementBuilder() { + return okForStatementBuilder; + } + + public boolean isOkForQuery() { + return okForQuery; + } + + public boolean isOkForUpdate() { + return okForUpdate; + } + + public boolean isOkForExecute() { + return okForExecute; + } + } + + /** + * Class which wraps information about a statement including the arguments and the generated SQL statement string. + */ + public static class StatementInfo { + + private final String statement; + private final List argList; + + private StatementInfo(String statement, List argList) { + this.argList = argList; + this.statement = statement; + } + + public String getStatement() { + return statement; + } + + public List getArgList() { + return argList; + } + } + + /** + * Enum which defines which type of where operation we are appending. + */ + protected enum WhereOperation { + FIRST("WHERE ", null), + AND("AND (", ") "), + OR("OR (", ") "), + // end + ; + + private final String before; + private final String after; + + private WhereOperation(String before, String after) { + this.before = before; + this.after = after; + } + + /** + * Append the necessary operators before the where statement. + */ + public void appendBefore(StringBuilder sb) { + if (before != null) { + sb.append(before); + } + } + + /** + * Append the necessary operators after the where statement. + */ + public void appendAfter(StringBuilder sb) { + if (after != null) { + sb.append(after); + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementExecutor.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementExecutor.java new file mode 100644 index 0000000..cf1e15c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/StatementExecutor.java @@ -0,0 +1,828 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; + +import com.j256.ormlite.dao.BaseDaoImpl; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DatabaseResultsMapper; +import com.j256.ormlite.dao.GenericRawResults; +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.dao.RawRowMapper; +import com.j256.ormlite.dao.RawRowObjectMapper; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DataType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.misc.TransactionManager; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.stmt.mapped.MappedCreate; +import com.j256.ormlite.stmt.mapped.MappedDelete; +import com.j256.ormlite.stmt.mapped.MappedDeleteCollection; +import com.j256.ormlite.stmt.mapped.MappedQueryForId; +import com.j256.ormlite.stmt.mapped.MappedRefresh; +import com.j256.ormlite.stmt.mapped.MappedUpdate; +import com.j256.ormlite.stmt.mapped.MappedUpdateId; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.TableInfo; + +/** + * Executes SQL statements for a particular table in a particular database. Basically a call through to various mapped + * statement methods. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public class StatementExecutor implements GenericRowMapper { + + private static Logger logger = LoggerFactory.getLogger(StatementExecutor.class); + private static final FieldType[] noFieldTypes = new FieldType[0]; + + private final DatabaseType databaseType; + private final TableInfo tableInfo; + private final Dao dao; + private MappedQueryForId mappedQueryForId; + private PreparedQuery preparedQueryForAll; + private MappedCreate mappedInsert; + private MappedUpdate mappedUpdate; + private MappedUpdateId mappedUpdateId; + private MappedDelete mappedDelete; + private MappedRefresh mappedRefresh; + private String countStarQuery; + private String ifExistsQuery; + private FieldType[] ifExistsFieldTypes; + private RawRowMapper rawRowMapper; + + private final ThreadLocal localIsInBatchMode = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return false; + } + }; + + /** + * Provides statements for various SQL operations. + */ + public StatementExecutor(DatabaseType databaseType, TableInfo tableInfo, Dao dao) { + this.databaseType = databaseType; + this.tableInfo = tableInfo; + this.dao = dao; + } + + /** + * Return the object associated with the id or null if none. This does a SQL + * {@code SELECT col1,col2,... FROM ... WHERE ... = id} type query. + */ + public T queryForId(DatabaseConnection databaseConnection, ID id, ObjectCache objectCache) throws SQLException { + if (mappedQueryForId == null) { + mappedQueryForId = MappedQueryForId.build(databaseType, tableInfo, null); + } + return mappedQueryForId.execute(databaseConnection, id, objectCache); + } + + /** + * Return the first object that matches the {@link PreparedStmt} or null if none. + */ + public T queryForFirst(DatabaseConnection databaseConnection, PreparedStmt preparedStmt, ObjectCache objectCache) + throws SQLException { + CompiledStatement compiledStatement = preparedStmt.compile(databaseConnection, StatementType.SELECT); + DatabaseResults results = null; + try { + results = compiledStatement.runQuery(objectCache); + if (results.first()) { + logger.debug("query-for-first of '{}' returned at least 1 result", preparedStmt.getStatement()); + return preparedStmt.mapRow(results); + } else { + logger.debug("query-for-first of '{}' returned at 0 results", preparedStmt.getStatement()); + return null; + } + } finally { + IOUtils.closeThrowSqlException(results, "results"); + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Return a list of all of the data in the table. Should be used carefully if the table is large. Consider using the + * {@link Dao#iterator} if this is the case. + */ + public List queryForAll(ConnectionSource connectionSource, ObjectCache objectCache) throws SQLException { + prepareQueryForAll(); + return query(connectionSource, preparedQueryForAll, objectCache); + } + + /** + * Return a long value which is the number of rows in the table. + */ + public long queryForCountStar(DatabaseConnection databaseConnection) throws SQLException { + if (countStarQuery == null) { + StringBuilder sb = new StringBuilder(64); + sb.append("SELECT COUNT(*) FROM "); + databaseType.appendEscapedEntityName(sb, tableInfo.getTableName()); + countStarQuery = sb.toString(); + } + long count = databaseConnection.queryForLong(countStarQuery); + logger.debug("query of '{}' returned {}", countStarQuery, count); + return count; + } + + /** + * Return a long value from a prepared query. + */ + public long queryForLong(DatabaseConnection databaseConnection, PreparedStmt preparedStmt) throws SQLException { + CompiledStatement compiledStatement = preparedStmt.compile(databaseConnection, StatementType.SELECT_LONG); + DatabaseResults results = null; + try { + results = compiledStatement.runQuery(null); + if (results.first()) { + return results.getLong(0); + } else { + throw new SQLException("No result found in queryForLong: " + preparedStmt.getStatement()); + } + } finally { + IOUtils.closeThrowSqlException(results, "results"); + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Return a long from a raw query with String[] arguments. + */ + public long queryForLong(DatabaseConnection databaseConnection, String query, String[] arguments) + throws SQLException { + logger.debug("executing raw query for long: {}", query); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("query arguments: {}", (Object) arguments); + } + CompiledStatement compiledStatement = null; + DatabaseResults results = null; + try { + compiledStatement = + databaseConnection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + assignStatementArguments(compiledStatement, arguments); + results = compiledStatement.runQuery(null); + if (results.first()) { + return results.getLong(0); + } else { + throw new SQLException("No result found in queryForLong: " + query); + } + } finally { + IOUtils.closeThrowSqlException(results, "results"); + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Return a list of all of the data in the table that matches the {@link PreparedStmt}. Should be used carefully if + * the table is large. Consider using the {@link Dao#iterator} if this is the case. + */ + public List query(ConnectionSource connectionSource, PreparedStmt preparedStmt, ObjectCache objectCache) + throws SQLException { + SelectIterator iterator = + buildIterator(/* no dao specified because no removes */null, connectionSource, preparedStmt, objectCache, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + try { + List results = new ArrayList(); + while (iterator.hasNextThrow()) { + results.add(iterator.nextThrow()); + } + logger.debug("query of '{}' returned {} results", preparedStmt.getStatement(), results.size()); + return results; + } finally { + IOUtils.closeThrowSqlException(iterator, "iterator"); + } + } + + /** + * Create and return a SelectIterator for the class using the default mapped query for all statement. + */ + public SelectIterator buildIterator(BaseDaoImpl classDao, ConnectionSource connectionSource, + int resultFlags, ObjectCache objectCache) throws SQLException { + prepareQueryForAll(); + return buildIterator(classDao, connectionSource, preparedQueryForAll, objectCache, resultFlags); + } + + /** + * Return a row mapper suitable for mapping 'select *' queries. + */ + public GenericRowMapper getSelectStarRowMapper() throws SQLException { + prepareQueryForAll(); + return preparedQueryForAll; + } + + /** + * Return a raw row mapper suitable for use with {@link Dao#queryRaw(String, RawRowMapper, String...)}. + */ + public RawRowMapper getRawRowMapper() { + if (rawRowMapper == null) { + rawRowMapper = new RawRowMapperImpl(tableInfo); + } + return rawRowMapper; + } + + /** + * Create and return an {@link SelectIterator} for the class using a prepared statement. + */ + public SelectIterator buildIterator(BaseDaoImpl classDao, ConnectionSource connectionSource, + PreparedStmt preparedStmt, ObjectCache objectCache, int resultFlags) throws SQLException { + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + CompiledStatement compiledStatement = null; + try { + compiledStatement = preparedStmt.compile(connection, StatementType.SELECT, resultFlags); + SelectIterator iterator = + new SelectIterator(tableInfo.getDataClass(), classDao, preparedStmt, connectionSource, + connection, compiledStatement, preparedStmt.getStatement(), objectCache); + connection = null; + compiledStatement = null; + return iterator; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + if (connection != null) { + connectionSource.releaseConnection(connection); + } + } + } + + /** + * Return a results object associated with an internal iterator that returns String[] results. + */ + public GenericRawResults queryRaw(ConnectionSource connectionSource, String query, String[] arguments, + ObjectCache objectCache) throws SQLException { + logger.debug("executing raw query for: {}", query); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("query arguments: {}", (Object) arguments); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + CompiledStatement compiledStatement = null; + try { + compiledStatement = + connection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + assignStatementArguments(compiledStatement, arguments); + GenericRawResults rawResults = + new RawResultsImpl(connectionSource, connection, query, String[].class, + compiledStatement, this, objectCache); + compiledStatement = null; + connection = null; + return rawResults; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + if (connection != null) { + connectionSource.releaseConnection(connection); + } + } + } + + /** + * Return a results object associated with an internal iterator is mapped by the user's rowMapper. + */ + public GenericRawResults queryRaw(ConnectionSource connectionSource, String query, + RawRowMapper rowMapper, String[] arguments, ObjectCache objectCache) throws SQLException { + logger.debug("executing raw query for: {}", query); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("query arguments: {}", (Object) arguments); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + CompiledStatement compiledStatement = null; + try { + compiledStatement = + connection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + assignStatementArguments(compiledStatement, arguments); + RawResultsImpl rawResults = + new RawResultsImpl(connectionSource, connection, query, String[].class, compiledStatement, + new UserRawRowMapper(rowMapper, this), objectCache); + compiledStatement = null; + connection = null; + return rawResults; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + if (connection != null) { + connectionSource.releaseConnection(connection); + } + } + } + + /** + * Return a results object associated with an internal iterator is mapped by the user's rowMapper. + */ + public GenericRawResults queryRaw(ConnectionSource connectionSource, String query, DataType[] columnTypes, + RawRowObjectMapper rowMapper, String[] arguments, ObjectCache objectCache) throws SQLException { + logger.debug("executing raw query for: {}", query); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("query arguments: {}", (Object) arguments); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + CompiledStatement compiledStatement = null; + try { + compiledStatement = + connection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + assignStatementArguments(compiledStatement, arguments); + RawResultsImpl rawResults = + new RawResultsImpl(connectionSource, connection, query, String[].class, compiledStatement, + new UserRawRowObjectMapper(rowMapper, columnTypes), objectCache); + compiledStatement = null; + connection = null; + return rawResults; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + if (connection != null) { + connectionSource.releaseConnection(connection); + } + } + } + + /** + * Return a results object associated with an internal iterator that returns Object[] results. + */ + public GenericRawResults queryRaw(ConnectionSource connectionSource, String query, + DataType[] columnTypes, String[] arguments, ObjectCache objectCache) throws SQLException { + logger.debug("executing raw query for: {}", query); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("query arguments: {}", (Object) arguments); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + CompiledStatement compiledStatement = null; + try { + compiledStatement = + connection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + assignStatementArguments(compiledStatement, arguments); + RawResultsImpl rawResults = + new RawResultsImpl(connectionSource, connection, query, Object[].class, + compiledStatement, new ObjectArrayRowMapper(columnTypes), objectCache); + compiledStatement = null; + connection = null; + return rawResults; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + if (connection != null) { + connectionSource.releaseConnection(connection); + } + } + } + + /** + * Return a results object associated with an internal iterator is mapped by the user's rowMapper. + */ + public GenericRawResults queryRaw(ConnectionSource connectionSource, String query, + DatabaseResultsMapper mapper, String[] arguments, ObjectCache objectCache) throws SQLException { + logger.debug("executing raw query for: {}", query); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("query arguments: {}", (Object) arguments); + } + DatabaseConnection connection = connectionSource.getReadOnlyConnection(); + CompiledStatement compiledStatement = null; + try { + compiledStatement = + connection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + assignStatementArguments(compiledStatement, arguments); + RawResultsImpl rawResults = + new RawResultsImpl(connectionSource, connection, query, Object[].class, compiledStatement, + new UserDatabaseResultsMapper(mapper), objectCache); + compiledStatement = null; + connection = null; + return rawResults; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + if (connection != null) { + connectionSource.releaseConnection(connection); + } + } + } + + /** + * Return the number of rows affected. + */ + public int updateRaw(DatabaseConnection connection, String statement, String[] arguments) throws SQLException { + logger.debug("running raw update statement: {}", statement); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("update arguments: {}", (Object) arguments); + } + CompiledStatement compiledStatement = + connection.compileStatement(statement, StatementType.UPDATE, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + try { + assignStatementArguments(compiledStatement, arguments); + return compiledStatement.runUpdate(); + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Return true if it worked else false. + */ + public int executeRawNoArgs(DatabaseConnection connection, String statement) throws SQLException { + logger.debug("running raw execute statement: {}", statement); + return connection.executeStatement(statement, DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + /** + * Return true if it worked else false. + */ + public int executeRaw(DatabaseConnection connection, String statement, String[] arguments) throws SQLException { + logger.debug("running raw execute statement: {}", statement); + if (arguments.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("execute arguments: {}", (Object) arguments); + } + CompiledStatement compiledStatement = + connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + try { + assignStatementArguments(compiledStatement, arguments); + return compiledStatement.runExecute(); + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Create a new entry in the database from an object. + */ + public int create(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException { + if (mappedInsert == null) { + mappedInsert = MappedCreate.build(databaseType, tableInfo); + } + int result = mappedInsert.insert(databaseType, databaseConnection, data, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Update an object in the database. + */ + public int update(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException { + if (mappedUpdate == null) { + mappedUpdate = MappedUpdate.build(databaseType, tableInfo); + } + int result = mappedUpdate.update(databaseConnection, data, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Update an object in the database to change its id to the newId parameter. + */ + public int updateId(DatabaseConnection databaseConnection, T data, ID newId, ObjectCache objectCache) + throws SQLException { + if (mappedUpdateId == null) { + mappedUpdateId = MappedUpdateId.build(databaseType, tableInfo); + } + int result = mappedUpdateId.execute(databaseConnection, data, newId, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Update rows in the database. + */ + public int update(DatabaseConnection databaseConnection, PreparedUpdate preparedUpdate) throws SQLException { + CompiledStatement compiledStatement = preparedUpdate.compile(databaseConnection, StatementType.UPDATE); + try { + int result = compiledStatement.runUpdate(); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Does a query for the object's Id and copies in each of the field values from the database to refresh the data + * parameter. + */ + public int refresh(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException { + if (mappedRefresh == null) { + mappedRefresh = MappedRefresh.build(databaseType, tableInfo); + } + return mappedRefresh.executeRefresh(databaseConnection, data, objectCache); + } + + /** + * Delete an object from the database. + */ + public int delete(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException { + if (mappedDelete == null) { + mappedDelete = MappedDelete.build(databaseType, tableInfo); + } + int result = mappedDelete.delete(databaseConnection, data, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Delete an object from the database by id. + */ + public int deleteById(DatabaseConnection databaseConnection, ID id, ObjectCache objectCache) throws SQLException { + if (mappedDelete == null) { + mappedDelete = MappedDelete.build(databaseType, tableInfo); + } + int result = mappedDelete.deleteById(databaseConnection, id, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Delete a collection of objects from the database. + */ + public int deleteObjects(DatabaseConnection databaseConnection, Collection datas, ObjectCache objectCache) + throws SQLException { + // have to build this on the fly because the collection has variable number of args + int result = + MappedDeleteCollection.deleteObjects(databaseType, tableInfo, databaseConnection, datas, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Delete a collection of objects from the database. + */ + public int deleteIds(DatabaseConnection databaseConnection, Collection ids, ObjectCache objectCache) + throws SQLException { + // have to build this on the fly because the collection has variable number of args + int result = MappedDeleteCollection.deleteIds(databaseType, tableInfo, databaseConnection, ids, objectCache); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } + + /** + * Delete rows that match the prepared statement. + */ + public int delete(DatabaseConnection databaseConnection, PreparedDelete preparedDelete) throws SQLException { + CompiledStatement compiledStatement = preparedDelete.compile(databaseConnection, StatementType.DELETE); + try { + int result = compiledStatement.runUpdate(); + if (dao != null && !localIsInBatchMode.get()) { + dao.notifyChanges(); + } + return result; + } finally { + IOUtils.closeThrowSqlException(compiledStatement, "compiled statement"); + } + } + + /** + * Call batch tasks inside of a connection which may, or may not, have been "saved". + */ + public CT callBatchTasks(ConnectionSource connectionSource, Callable callable) throws SQLException { + if (connectionSource.isSingleConnection()) { + synchronized (this) { + return doCallBatchTasks(connectionSource, callable); + } + } else { + return doCallBatchTasks(connectionSource, callable); + } + } + + private CT doCallBatchTasks(ConnectionSource connectionSource, Callable callable) throws SQLException { + boolean saved = false; + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + /* + * We are using a thread-local boolean to detect whether we are in the middle of running a number of + * changes. This disables the dao change notification for every batched call. + */ + localIsInBatchMode.set(true); + /* + * We need to save the connection because we are going to be disabling auto-commit on it and we don't want + * pooled connection factories to give us another connection where auto-commit might still be enabled. + */ + saved = connectionSource.saveSpecialConnection(connection); + return doCallBatchTasks(connection, saved, callable); + } finally { + if (saved) { + connectionSource.clearSpecialConnection(connection); + } + connectionSource.releaseConnection(connection); + localIsInBatchMode.set(false); + if (dao != null) { + // only at the end is the DAO notified of changes + dao.notifyChanges(); + } + } + } + + private CT doCallBatchTasks(DatabaseConnection connection, boolean saved, Callable callable) + throws SQLException { + if (databaseType.isBatchUseTransaction()) { + return TransactionManager.callInTransaction(connection, saved, databaseType, callable); + } + boolean resetAutoCommit = false; + try { + if (connection.isAutoCommitSupported()) { + if (connection.isAutoCommit()) { + // disable auto-commit mode if supported and enabled at start + connection.setAutoCommit(false); + resetAutoCommit = true; + logger.debug("disabled auto-commit on table {} before batch tasks", tableInfo.getTableName()); + } + } + try { + return callable.call(); + } catch (SQLException e) { + throw e; + } catch (Exception e) { + throw SqlExceptionUtil.create("Batch tasks callable threw non-SQL exception", e); + } + } finally { + if (resetAutoCommit) { + /** + * Try to restore if we are in auto-commit mode. + * + * NOTE: we do _not_ have to do a commit here. According to {@link Connection#setAutoCommit(boolean)}, + * this will start a transaction when auto-commit is turned off and it will be committed here. + */ + connection.setAutoCommit(true); + logger.debug("re-enabled auto-commit on table {} after batch tasks", tableInfo.getTableName()); + } + } + } + + public String[] mapRow(DatabaseResults results) throws SQLException { + int columnN = results.getColumnCount(); + String[] result = new String[columnN]; + for (int colC = 0; colC < columnN; colC++) { + result[colC] = results.getString(colC); + } + return result; + } + + public boolean ifExists(DatabaseConnection connection, ID id) throws SQLException { + if (ifExistsQuery == null) { + QueryBuilder qb = new QueryBuilder(databaseType, tableInfo, dao); + qb.selectRaw("COUNT(*)"); + /* + * NOTE: bit of a hack here because the select arg is never used but it _can't_ be a constant because we set + * field-name and field-type on it. + */ + qb.where().eq(tableInfo.getIdField().getColumnName(), new SelectArg()); + ifExistsQuery = qb.prepareStatementString(); + ifExistsFieldTypes = new FieldType[] { tableInfo.getIdField() }; + } + Object idSqlArg = tableInfo.getIdField().convertJavaFieldToSqlArgValue(id); + long count = connection.queryForLong(ifExistsQuery, new Object[] { idSqlArg }, ifExistsFieldTypes); + logger.debug("query of '{}' returned {}", ifExistsQuery, count); + return (count != 0); + } + + private void assignStatementArguments(CompiledStatement compiledStatement, String[] arguments) throws SQLException { + for (int i = 0; i < arguments.length; i++) { + compiledStatement.setObject(i, arguments[i], SqlType.STRING); + } + } + + private void prepareQueryForAll() throws SQLException { + if (preparedQueryForAll == null) { + preparedQueryForAll = new QueryBuilder(databaseType, tableInfo, dao).prepare(); + } + } + + /** + * Map raw results to return a user object from a String array. + */ + private static class UserRawRowMapper implements GenericRowMapper { + + private final RawRowMapper mapper; + private final GenericRowMapper stringRowMapper; + private String[] columnNames; + + public UserRawRowMapper(RawRowMapper mapper, GenericRowMapper stringMapper) { + this.mapper = mapper; + this.stringRowMapper = stringMapper; + } + + public UO mapRow(DatabaseResults results) throws SQLException { + String[] stringResults = stringRowMapper.mapRow(results); + return mapper.mapRow(getColumnNames(results), stringResults); + } + + private String[] getColumnNames(DatabaseResults results) throws SQLException { + if (columnNames != null) { + return columnNames; + } + columnNames = results.getColumnNames(); + return columnNames; + } + } + + /** + * Map raw results to return a user object from an Object array. + */ + private static class UserRawRowObjectMapper implements GenericRowMapper { + + private final RawRowObjectMapper mapper; + private final DataType[] columnTypes; + private String[] columnNames; + + public UserRawRowObjectMapper(RawRowObjectMapper mapper, DataType[] columnTypes) { + this.mapper = mapper; + this.columnTypes = columnTypes; + } + + public UO mapRow(DatabaseResults results) throws SQLException { + int columnN = results.getColumnCount(); + Object[] objectResults = new Object[columnN]; + for (int colC = 0; colC < columnN; colC++) { + if (colC >= columnTypes.length) { + objectResults[colC] = null; + } else { + objectResults[colC] = columnTypes[colC].getDataPersister().resultToJava(null, results, colC); + } + } + return mapper.mapRow(getColumnNames(results), columnTypes, objectResults); + } + + private String[] getColumnNames(DatabaseResults results) throws SQLException { + if (columnNames != null) { + return columnNames; + } + columnNames = results.getColumnNames(); + return columnNames; + } + } + + /** + * Map raw results to return Object[]. + */ + private static class ObjectArrayRowMapper implements GenericRowMapper { + + private final DataType[] columnTypes; + + public ObjectArrayRowMapper(DataType[] columnTypes) { + this.columnTypes = columnTypes; + } + + public Object[] mapRow(DatabaseResults results) throws SQLException { + int columnN = results.getColumnCount(); + Object[] result = new Object[columnN]; + for (int colC = 0; colC < columnN; colC++) { + DataType dataType; + if (colC >= columnTypes.length) { + dataType = DataType.STRING; + } else { + dataType = columnTypes[colC]; + } + result[colC] = dataType.getDataPersister().resultToJava(null, results, colC); + } + return result; + } + } + + /** + * Mapper which uses the {@link DatabaseResults} directly. + */ + private static class UserDatabaseResultsMapper implements GenericRowMapper { + + public final DatabaseResultsMapper mapper; + + private UserDatabaseResultsMapper(DatabaseResultsMapper mapper) { + this.mapper = mapper; + } + + public UO mapRow(DatabaseResults results) throws SQLException { + return mapper.mapRow(results); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ThreadLocalSelectArg.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ThreadLocalSelectArg.java new file mode 100644 index 0000000..cae89d2 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/ThreadLocalSelectArg.java @@ -0,0 +1,64 @@ +package com.j256.ormlite.stmt; + +import com.j256.ormlite.field.SqlType; + +/** + * Like {@link SelectArg} but using a {@link ThreadLocal} internally to improve reentrance so that multiple threads can + * use the same compiled statement. + * + * @author graywatson + */ +public class ThreadLocalSelectArg extends BaseArgumentHolder { + + private ThreadLocal threadValue = new ThreadLocal(); + + public ThreadLocalSelectArg() { + super(); + // value set later + } + + public ThreadLocalSelectArg(String columnName, Object value) { + super(columnName); + setValue(value); + } + + public ThreadLocalSelectArg(SqlType sqlType, Object value) { + super(sqlType); + setValue(value); + } + + public ThreadLocalSelectArg(Object value) { + setValue(value); + } + + @Override + protected Object getValue() { + ValueWrapper wrapper = threadValue.get(); + if (wrapper == null) { + return null; + } else { + return wrapper.value; + } + } + + @Override + public void setValue(Object value) { + threadValue.set(new ValueWrapper(value)); + } + + @Override + protected boolean isValueSet() { + return threadValue.get() != null; + } + + /** + * Value wrapper so we can differentiate between a null value and no value. Since we need to do this on a per thread + * basis, this can't be a volatile field or something. + */ + private static class ValueWrapper { + Object value; + public ValueWrapper(Object value) { + this.value = value; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/UpdateBuilder.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/UpdateBuilder.java new file mode 100644 index 0000000..91f0f97 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/UpdateBuilder.java @@ -0,0 +1,164 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.query.Clause; +import com.j256.ormlite.stmt.query.SetExpression; +import com.j256.ormlite.stmt.query.SetValue; +import com.j256.ormlite.table.TableInfo; + +/** + * Assists in building sql UPDATE statements for a particular table in a particular database. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public class UpdateBuilder extends StatementBuilder { + + private List updateClauseList = null; + // NOTE: anything added here should be added to the clear() method below + + public UpdateBuilder(DatabaseType databaseType, TableInfo tableInfo, Dao dao) { + super(databaseType, tableInfo, dao, StatementType.UPDATE); + } + + /** + * Build and return a prepared update that can be used by {@link Dao#update(PreparedUpdate)} method. If you change + * the where or make other calls you will need to re-call this method to re-prepare the statement for execution. + */ + public PreparedUpdate prepare() throws SQLException { + return super.prepareStatement(null); + } + + /** + * Add a column to be set to a value for UPDATE statements. This will generate something like columnName = 'value' + * with the value escaped if necessary. + */ + public UpdateBuilder updateColumnValue(String columnName, Object value) throws SQLException { + FieldType fieldType = verifyColumnName(columnName); + if (fieldType.isForeignCollection()) { + throw new SQLException("Can't update foreign colletion field: " + columnName); + } + addUpdateColumnToList(columnName, new SetValue(columnName, fieldType, value)); + return this; + } + + /** + * Add a column to be set to a value for UPDATE statements. This will generate something like 'columnName = + * expression' where the expression is built by the caller. + * + *

+ * The expression should have any strings escaped using the {@link #escapeValue(String)} or + * {@link #escapeValue(StringBuilder, String)} methods and should have any column names escaped using the + * {@link #escapeColumnName(String)} or {@link #escapeColumnName(StringBuilder, String)} methods. + *

+ */ + public UpdateBuilder updateColumnExpression(String columnName, String expression) throws SQLException { + FieldType fieldType = verifyColumnName(columnName); + if (fieldType.isForeignCollection()) { + throw new SQLException("Can't update foreign colletion field: " + columnName); + } + addUpdateColumnToList(columnName, new SetExpression(columnName, fieldType, expression)); + return this; + } + + /** + * When you are building the expression for {@link #updateColumnExpression(String, String)}, you may need to escape + * column names since they may be reserved words to the database. This will help you by adding escape characters + * around the word. + */ + public void escapeColumnName(StringBuilder sb, String columnName) { + databaseType.appendEscapedEntityName(sb, columnName); + } + + /** + * Same as {@link #escapeColumnName(StringBuilder, String)} but it will return the escaped string. The StringBuilder + * method is more efficient since this method creates a StringBuilder internally. + */ + public String escapeColumnName(String columnName) { + StringBuilder sb = new StringBuilder(columnName.length() + 4); + databaseType.appendEscapedEntityName(sb, columnName); + return sb.toString(); + } + + /** + * When you are building the expression for {@link #updateColumnExpression(String, String)}, you may need to escape + * values since they may be reserved words to the database. Numbers should not be escaped. This will help you by + * adding escape characters around the word. + */ + public void escapeValue(StringBuilder sb, String value) { + databaseType.appendEscapedWord(sb, value); + } + + /** + * Same as {@link #escapeValue(StringBuilder, String)} but it will return the escaped string. Numbers should not be + * escaped. The StringBuilder method is more efficient since this method creates a StringBuilder internally. + */ + public String escapeValue(String value) { + StringBuilder sb = new StringBuilder(value.length() + 4); + databaseType.appendEscapedWord(sb, value); + return sb.toString(); + } + + /** + * A short cut to {@link Dao#update(PreparedUpdate)}. + */ + public int update() throws SQLException { + return dao.update(prepare()); + } + + /** + * @deprecated Renamed to be {@link #reset()}. + */ + @Deprecated + @Override + public void clear() { + reset(); + } + + @Override + public void reset() { + super.reset(); + updateClauseList = null; + } + + @Override + protected void appendStatementStart(StringBuilder sb, List argList) throws SQLException { + if (updateClauseList == null || updateClauseList.isEmpty()) { + throw new IllegalArgumentException("UPDATE statements must have at least one SET column"); + } + sb.append("UPDATE "); + databaseType.appendEscapedEntityName(sb, tableInfo.getTableName()); + sb.append(" SET "); + boolean first = true; + for (Clause clause : updateClauseList) { + if (first) { + first = false; + } else { + sb.append(','); + } + clause.appendSql(databaseType, null, sb, argList); + } + } + + @Override + protected void appendStatementEnd(StringBuilder sb, List argList) { + // noop + } + + private void addUpdateColumnToList(String columnName, Clause clause) { + if (updateClauseList == null) { + updateClauseList = new ArrayList(); + } + updateClauseList.add(clause); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/Where.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/Where.java new file mode 100644 index 0000000..c8a73fa --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/Where.java @@ -0,0 +1,690 @@ +package com.j256.ormlite.stmt; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.dao.CloseableIterator; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.GenericRawResults; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.QueryBuilder.InternalQueryBuilderWrapper; +import com.j256.ormlite.stmt.query.Between; +import com.j256.ormlite.stmt.query.Clause; +import com.j256.ormlite.stmt.query.Exists; +import com.j256.ormlite.stmt.query.In; +import com.j256.ormlite.stmt.query.InSubQuery; +import com.j256.ormlite.stmt.query.IsNotNull; +import com.j256.ormlite.stmt.query.IsNull; +import com.j256.ormlite.stmt.query.ManyClause; +import com.j256.ormlite.stmt.query.NeedsFutureClause; +import com.j256.ormlite.stmt.query.Not; +import com.j256.ormlite.stmt.query.Raw; +import com.j256.ormlite.stmt.query.SimpleComparison; +import com.j256.ormlite.table.TableInfo; + +/** + *

+ * Manages the various clauses that make up the WHERE part of a SQL statement. You get one of these when you call + * {@link StatementBuilder#where} or you can set the where clause by calling {@link StatementBuilder#setWhere}. + *

+ * + *

+ * Here's a page with a good tutorial of SQL commands. + *

+ * + *

+ * To create a query which looks up an account by name and password you would do the following: + *

+ * + *
+ * QueryBuilder<Account, String> qb = accountDao.queryBuilder();
+ * Where where = qb.where();
+ * // the name field must be equal to "foo"
+ * where.eq(Account.NAME_FIELD_NAME, "foo");
+ * // and
+ * where.and();
+ * // the password field must be equal to "_secret"
+ * where.eq(Account.PASSWORD_FIELD_NAME, "_secret");
+ * PreparedQuery<Account, String> preparedQuery = qb.prepareQuery();
+ * 
+ * + *

+ * In this example, the SQL query that will be generated will be approximately: + *

+ * + *
+ * SELECT * FROM account WHERE (name = 'foo' AND passwd = '_secret')
+ * 
+ * + *

+ * If you'd rather chain the methods onto one line (like StringBuilder), this can also be written as: + *

+ * + *
+ * queryBuilder.where().eq(Account.NAME_FIELD_NAME, "foo").and().eq(Account.PASSWORD_FIELD_NAME, "_secret");
+ * 
+ * + *

+ * If you'd rather use parens and the like then you can call: + *

+ * + *
+ * Where where = queryBuilder.where();
+ * where.and(where.eq(Account.NAME_FIELD_NAME, "foo"), where.eq(Account.PASSWORD_FIELD_NAME, "_secret"));
+ * 
+ * + *

+ * All three of the above call formats produce the same SQL. For complex queries that mix ANDs and ORs, the last format + * will be necessary to get the grouping correct. For example, here's a complex query: + *

+ * + *
+ * Where where = queryBuilder.where();
+ * where.or(where.and(where.eq(Account.NAME_FIELD_NAME, "foo"), where.eq(Account.PASSWORD_FIELD_NAME, "_secret")),
+ * 		where.and(where.eq(Account.NAME_FIELD_NAME, "bar"), where.eq(Account.PASSWORD_FIELD_NAME, "qwerty")));
+ * 
+ * + *

+ * This produces the following approximate SQL: + *

+ * + *
+ * SELECT * FROM account WHERE ((name = 'foo' AND passwd = '_secret') OR (name = 'bar' AND passwd = 'qwerty'))
+ * 
+ * + * @author graywatson + */ +public class Where { + + private final static int CLAUSE_STACK_START_SIZE = 4; + + private final TableInfo tableInfo; + private final StatementBuilder statementBuilder; + private final FieldType idFieldType; + private final String idColumnName; + private final DatabaseType databaseType; + + private Clause[] clauseStack = new Clause[CLAUSE_STACK_START_SIZE]; + private int clauseStackLevel; + private NeedsFutureClause needsFuture = null; + + Where(TableInfo tableInfo, StatementBuilder statementBuilder, DatabaseType databaseType) { + // limit the constructor scope + this.tableInfo = tableInfo; + this.statementBuilder = statementBuilder; + this.idFieldType = tableInfo.getIdField(); + if (idFieldType == null) { + this.idColumnName = null; + } else { + this.idColumnName = idFieldType.getColumnName(); + } + this.databaseType = databaseType; + } + + /** + * AND operation which takes the previous clause and the next clause and AND's them together. + */ + public Where and() { + ManyClause clause = new ManyClause(pop("AND"), ManyClause.AND_OPERATION); + push(clause); + addNeedsFuture(clause); + return this; + } + + /** + * AND operation which takes 2 (or more) arguments and AND's them together. + * + *

+ * NOTE: There is no guarantee of the order of the clauses that are generated in the final query. + *

+ *

+ * NOTE: I can't remove the generics code warning that can be associated with this method. You can instead + * use the {@link #and(int)} method. + *

+ */ + public Where and(Where first, Where second, Where... others) { + Clause[] clauses = buildClauseArray(others, "AND"); + Clause secondClause = pop("AND"); + Clause firstClause = pop("AND"); + addClause(new ManyClause(firstClause, secondClause, clauses, ManyClause.AND_OPERATION)); + return this; + } + + /** + * This method needs to be used carefully. This will absorb a number of clauses that were registered previously with + * calls to {@link Where#eq(String, Object)} or other methods and will string them together with AND's. There is no + * way to verify the number of previous clauses so the programmer has to count precisely. + * + *

+ * NOTE: There is no guarantee of the order of the clauses that are generated in the final query. + *

+ * + *

+ * NOTE: This will throw an exception if numClauses is 0 but will work with 1 or more. + *

+ */ + public Where and(int numClauses) { + if (numClauses == 0) { + throw new IllegalArgumentException("Must have at least one clause in and(numClauses)"); + } + Clause[] clauses = new Clause[numClauses]; + for (int i = numClauses - 1; i >= 0; i--) { + clauses[i] = pop("AND"); + } + addClause(new ManyClause(clauses, ManyClause.AND_OPERATION)); + return this; + } + + /** + * Add a BETWEEN clause so the column must be between the low and high parameters. + */ + public Where between(String columnName, Object low, Object high) throws SQLException { + addClause(new Between(columnName, findColumnFieldType(columnName), low, high)); + return this; + } + + /** + * Add a '=' clause so the column must be equal to the value. + */ + public Where eq(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.EQUAL_TO_OPERATION)); + return this; + } + + /** + * Add a '>=' clause so the column must be greater-than or equals-to the value. + */ + public Where ge(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.GREATER_THAN_EQUAL_TO_OPERATION)); + return this; + } + + /** + * Add a '>' clause so the column must be greater-than the value. + */ + public Where gt(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.GREATER_THAN_OPERATION)); + return this; + } + + /** + * Add a IN clause so the column must be equal-to one of the objects from the list passed in. + */ + public Where in(String columnName, Iterable objects) throws SQLException { + addClause(new In(columnName, findColumnFieldType(columnName), objects, true)); + return this; + } + + /** + * Same as {@link #in(String, Iterable)} except with a NOT IN clause. + */ + public Where notIn(String columnName, Iterable objects) throws SQLException { + addClause(new In(columnName, findColumnFieldType(columnName), objects, false)); + return this; + } + + /** + * Add a IN clause so the column must be equal-to one of the objects passed in. + */ + public Where in(String columnName, Object... objects) throws SQLException { + return in(true, columnName, objects); + } + + /** + * Same as {@link #in(String, Object...)} except with a NOT IN clause. + */ + public Where notIn(String columnName, Object... objects) throws SQLException { + return in(false, columnName, objects); + } + + /** + * Add a IN clause which makes sure the column is in one of the columns returned from a sub-query inside of + * parenthesis. The QueryBuilder must return 1 and only one column which can be set with the + * {@link QueryBuilder#selectColumns(String...)} method calls. That 1 argument must match the SQL type of the + * column-name passed to this method. + * + *

+ * NOTE: The sub-query will be prepared at the same time that the outside query is. + *

+ */ + public Where in(String columnName, QueryBuilder subQueryBuilder) throws SQLException { + return in(true, columnName, subQueryBuilder); + } + + /** + * Same as {@link #in(String, QueryBuilder)} except with a NOT IN clause. + */ + public Where notIn(String columnName, QueryBuilder subQueryBuilder) throws SQLException { + return in(false, columnName, subQueryBuilder); + } + + /** + * Add a EXISTS clause with a sub-query inside of parenthesis. + * + *

+ * NOTE: The sub-query will be prepared at the same time that the outside query is. + *

+ */ + public Where exists(QueryBuilder subQueryBuilder) { + // we do this to turn off the automatic addition of the ID column in the select column list + subQueryBuilder.enableInnerQuery(); + addClause(new Exists(new InternalQueryBuilderWrapper(subQueryBuilder))); + return this; + } + + /** + * Add a 'IS NULL' clause so the column must be null. '=' NULL does not work. + */ + public Where isNull(String columnName) throws SQLException { + addClause(new IsNull(columnName, findColumnFieldType(columnName))); + return this; + } + + /** + * Add a 'IS NOT NULL' clause so the column must not be null. '<>' NULL does not work. + */ + public Where isNotNull(String columnName) throws SQLException { + addClause(new IsNotNull(columnName, findColumnFieldType(columnName))); + return this; + } + + /** + * Add a '<=' clause so the column must be less-than or equals-to the value. + */ + public Where le(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.LESS_THAN_EQUAL_TO_OPERATION)); + return this; + } + + /** + * Add a '<' clause so the column must be less-than the value. + */ + public Where lt(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.LESS_THAN_OPERATION)); + return this; + } + + /** + * Add a LIKE clause so the column must mach the value using '%' patterns. + */ + public Where like(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.LIKE_OPERATION)); + return this; + } + + /** + * Add a '<>' clause so the column must be not-equal-to the value. + */ + public Where ne(String columnName, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, + SimpleComparison.NOT_EQUAL_TO_OPERATION)); + return this; + } + + /** + * Used to NOT the next clause specified. + */ + public Where not() { + /* + * Special circumstance here when we have a needs future with a not. Something like and().not().like(...). In + * this case we satisfy the and()'s future as the not() but the not() becomes the new needs-future. + */ + Not not = new Not(); + addClause(not); + addNeedsFuture(not); + return this; + } + + /** + * Used to NOT the argument clause specified. + */ + public Where not(Where comparison) { + addClause(new Not(pop("NOT"))); + return this; + } + + /** + * OR operation which takes the previous clause and the next clause and OR's them together. + */ + public Where or() { + ManyClause clause = new ManyClause(pop("OR"), ManyClause.OR_OPERATION); + push(clause); + addNeedsFuture(clause); + return this; + } + + /** + * OR operation which takes 2 arguments and OR's them together. + * + *

+ * NOTE: There is no guarantee of the order of the clauses that are generated in the final query. + *

+ *

+ * NOTE: I can't remove the generics code warning that can be associated with this method. You can instead + * use the {@link #or(int)} method. + *

+ */ + public Where or(Where left, Where right, Where... others) { + Clause[] clauses = buildClauseArray(others, "OR"); + Clause secondClause = pop("OR"); + Clause firstClause = pop("OR"); + addClause(new ManyClause(firstClause, secondClause, clauses, ManyClause.OR_OPERATION)); + return this; + } + + /** + * This method needs to be used carefully. This will absorb a number of clauses that were registered previously with + * calls to {@link Where#eq(String, Object)} or other methods and will string them together with OR's. There is no + * way to verify the number of previous clauses so the programmer has to count precisely. + * + *

+ * NOTE: There is no guarantee of the order of the clauses that are generated in the final query. + *

+ * + *

+ * NOTE: This will throw an exception if numClauses is 0 but will work with 1 or more. + *

+ */ + public Where or(int numClauses) { + if (numClauses == 0) { + throw new IllegalArgumentException("Must have at least one clause in or(numClauses)"); + } + Clause[] clauses = new Clause[numClauses]; + for (int i = numClauses - 1; i >= 0; i--) { + clauses[i] = pop("OR"); + } + addClause(new ManyClause(clauses, ManyClause.OR_OPERATION)); + return this; + } + + /** + * Add a clause where the ID is equal to the argument. + */ + public Where idEq(ID id) throws SQLException { + if (idColumnName == null) { + throw new SQLException("Object has no id column specified"); + } + addClause(new SimpleComparison(idColumnName, idFieldType, id, SimpleComparison.EQUAL_TO_OPERATION)); + return this; + } + + /** + * Add a clause where the ID is from an existing object. + */ + public Where idEq(Dao dataDao, OD data) throws SQLException { + if (idColumnName == null) { + throw new SQLException("Object has no id column specified"); + } + addClause(new SimpleComparison(idColumnName, idFieldType, dataDao.extractId(data), + SimpleComparison.EQUAL_TO_OPERATION)); + return this; + } + + /** + * Add a raw statement as part of the where that can be anything that the database supports. Using more structured + * methods is recommended but this gives more control over the query and allows you to utilize database specific + * features. + * + * @param rawStatement + * The statement that we should insert into the WHERE. + * + * @param args + * Optional arguments that correspond to any ? specified in the rawStatement. Each of the arguments must + * have either the corresponding columnName or the sql-type set. WARNING, you cannot use the + * {@code SelectArg("columnName")} constructor since that sets the _value_, not the name. Use + * {@code new SelectArg("column-name", null);}. + */ + public Where raw(String rawStatement, ArgumentHolder... args) { + for (ArgumentHolder arg : args) { + String columnName = arg.getColumnName(); + if (columnName == null) { + if (arg.getSqlType() == null) { + throw new IllegalArgumentException("Either the column name or SqlType must be set on each argument"); + } + } else { + arg.setMetaInfo(findColumnFieldType(columnName)); + } + } + addClause(new Raw(rawStatement, args)); + return this; + } + + /** + * Make a comparison where the operator is specified by the caller. It is up to the caller to specify an appropriate + * operator for the database and that it be formatted correctly. + */ + public Where rawComparison(String columnName, String rawOperator, Object value) throws SQLException { + addClause(new SimpleComparison(columnName, findColumnFieldType(columnName), value, rawOperator)); + return this; + } + + /** + * A short-cut for calling {@link QueryBuilder#prepare()}. + */ + public PreparedQuery prepare() throws SQLException { + return statementBuilder.prepareStatement(null); + } + + /** + * A short-cut for calling {@link QueryBuilder#query()}. + */ + public List query() throws SQLException { + return checkQueryBuilderMethod("query()").query(); + } + + /** + * A short-cut for calling {@link QueryBuilder#queryRaw()}. + */ + public GenericRawResults queryRaw() throws SQLException { + return checkQueryBuilderMethod("queryRaw()").queryRaw(); + } + + /** + * A short-cut for calling {@link QueryBuilder#queryForFirst()}. + */ + public T queryForFirst() throws SQLException { + return checkQueryBuilderMethod("queryForFirst()").queryForFirst(); + } + + /** + * A short-cut for calling {@link QueryBuilder#queryRawFirst()}. + */ + public String[] queryRawFirst() throws SQLException { + return checkQueryBuilderMethod("queryRawFirst()").queryRawFirst(); + } + + /** + * A short-cut for calling {@link QueryBuilder#countOf()}. + */ + public long countOf() throws SQLException { + return checkQueryBuilderMethod("countOf()").countOf(); + } + + /** + * A short-cut for calling {@link QueryBuilder#iterator()}. + */ + public CloseableIterator iterator() throws SQLException { + return checkQueryBuilderMethod("iterator()").iterator(); + } + + /** + * @deprecated Should now use {@link #reset()}. + */ + @Deprecated + public Where clear() { + return reset(); + } + + /** + * Reset the Where object so it can be re-used. + */ + public Where reset() { + for (int i = 0; i < clauseStackLevel; i++) { + // help with gc + clauseStack[i] = null; + } + clauseStackLevel = 0; + return this; + } + + /** + * Returns the associated SQL WHERE statement. + */ + public String getStatement() throws SQLException { + StringBuilder sb = new StringBuilder(); + appendSql(null, sb, new ArrayList()); + return sb.toString(); + } + + /** + * Used by the internal classes to add the where SQL to the {@link StringBuilder}. + * + * @param tableName + * Name of the table to prepend to any column names or null to be ignored. + */ + void appendSql(String tableName, StringBuilder sb, List columnArgList) throws SQLException { + if (clauseStackLevel == 0) { + throw new IllegalStateException("No where clauses defined. Did you miss a where operation?"); + } + if (clauseStackLevel != 1) { + throw new IllegalStateException( + "Both the \"left-hand\" and \"right-hand\" clauses have been defined. Did you miss an AND or OR?"); + } + if (needsFuture != null) { + throw new IllegalStateException( + "The SQL statement has not been finished since there are previous operations still waiting for clauses."); + } + + // we don't pop here because we may want to run the query multiple times + peek().appendSql(databaseType, tableName, sb, columnArgList); + } + + @Override + public String toString() { + if (clauseStackLevel == 0) { + return "empty where clause"; + } else { + Clause clause = peek(); + return "where clause: " + clause; + } + } + + private QueryBuilder checkQueryBuilderMethod(String methodName) throws SQLException { + if (statementBuilder instanceof QueryBuilder) { + return (QueryBuilder) statementBuilder; + } else { + throw new SQLException("Cannot call " + methodName + " on a statement of type " + + statementBuilder.getType()); + } + } + + private Where in(boolean in, String columnName, Object... objects) throws SQLException { + if (objects.length == 1) { + if (objects[0].getClass().isArray()) { + throw new IllegalArgumentException("Object argument to " + (in ? "IN" : "notId") + + " seems to be an array within an array"); + } + if (objects[0] instanceof Where) { + throw new IllegalArgumentException("Object argument to " + (in ? "IN" : "notId") + + " seems to be a Where object, did you mean the QueryBuilder?"); + } + if (objects[0] instanceof PreparedStmt) { + throw new IllegalArgumentException("Object argument to " + (in ? "IN" : "notId") + + " seems to be a prepared statement, did you mean the QueryBuilder?"); + } + } + addClause(new In(columnName, findColumnFieldType(columnName), objects, in)); + return this; + } + + private Where in(boolean in, String columnName, QueryBuilder subQueryBuilder) throws SQLException { + if (subQueryBuilder.getSelectColumnCount() != 1) { + if (subQueryBuilder.getSelectColumnCount() == 0) { + throw new SQLException("Inner query must have only 1 select column specified instead of *"); + } else { + throw new SQLException("Inner query must have only 1 select column specified instead of " + + subQueryBuilder.getSelectColumnCount() + ": " + subQueryBuilder.getSelectColumnsAsString()); + } + } + // we do this to turn off the automatic addition of the ID column in the select column list + subQueryBuilder.enableInnerQuery(); + addClause(new InSubQuery(columnName, findColumnFieldType(columnName), new InternalQueryBuilderWrapper( + subQueryBuilder), in)); + return this; + } + + private Clause[] buildClauseArray(Where[] others, String label) { + Clause[] clauses; + if (others.length == 0) { + clauses = null; + } else { + clauses = new Clause[others.length]; + // fill in reverse order + for (int i = others.length - 1; i >= 0; i--) { + clauses[i] = pop(label); + } + } + return clauses; + } + + private void addNeedsFuture(NeedsFutureClause clause) { + if (needsFuture != null) { + throw new IllegalStateException(needsFuture + " is already waiting for a future clause, can't add: " + + clause); + } + needsFuture = clause; + } + + private void addClause(Clause clause) { + if (needsFuture == null) { + push(clause); + } else { + // we have a binary statement which was called before the right clause was defined + needsFuture.setMissingClause(clause); + needsFuture = null; + } + } + + private FieldType findColumnFieldType(String columnName) { + return tableInfo.getFieldTypeByColumnName(columnName); + } + + private void push(Clause clause) { + // if the stack is full then we need to grow it + if (clauseStackLevel == clauseStack.length) { + // double its size each time + Clause[] newStack = new Clause[clauseStackLevel * 2]; + // copy the entries over to the new stack + for (int i = 0; i < clauseStackLevel; i++) { + newStack[i] = clauseStack[i]; + // to help gc + clauseStack[i] = null; + } + clauseStack = newStack; + } + clauseStack[clauseStackLevel++] = clause; + } + + private Clause pop(String label) { + if (clauseStackLevel == 0) { + throw new IllegalStateException("Expecting there to be a clause already defined for '" + label + + "' operation"); + } + Clause clause = clauseStack[--clauseStackLevel]; + // to help gc + clauseStack[clauseStackLevel] = null; + return clause; + } + + private Clause peek() { + return clauseStack[clauseStackLevel - 1]; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedQuery.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedQuery.java new file mode 100644 index 0000000..cec13ff --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedQuery.java @@ -0,0 +1,106 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import com.j256.ormlite.dao.BaseForeignCollection; +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.support.DatabaseResults; +import com.j256.ormlite.table.TableInfo; + +/** + * Abstract mapped statement for queries which handle the creating of a new object and the row mapping functionality. + * + * @author graywatson + */ +public abstract class BaseMappedQuery extends BaseMappedStatement implements GenericRowMapper { + + protected final FieldType[] resultsFieldTypes; + // cache of column names to results position + private Map columnPositions = null; + private Object parent = null; + private Object parentId = null; + + protected BaseMappedQuery(TableInfo tableInfo, String statement, FieldType[] argFieldTypes, + FieldType[] resultsFieldTypes) { + super(tableInfo, statement, argFieldTypes); + this.resultsFieldTypes = resultsFieldTypes; + } + + public T mapRow(DatabaseResults results) throws SQLException { + Map colPosMap; + if (columnPositions == null) { + colPosMap = new HashMap(); + } else { + colPosMap = columnPositions; + } + + ObjectCache objectCache = results.getObjectCache(); + if (objectCache != null) { + Object id = idField.resultToJava(results, colPosMap); + T cachedInstance = objectCache.get(clazz, id); + if (cachedInstance != null) { + // if we have a cached instance for this id then return it + return cachedInstance; + } + } + + // create our instance + T instance = tableInfo.createObject(); + // populate its fields + Object id = null; + boolean foreignCollections = false; + for (FieldType fieldType : resultsFieldTypes) { + if (fieldType.isForeignCollection()) { + foreignCollections = true; + } else { + Object val = fieldType.resultToJava(results, colPosMap); + /* + * This is pretty subtle. We introduced multiple foreign fields to the same type which use the {@link + * ForeignCollectionField} foreignColumnName field. The bug that was created was that all the fields + * were then set with the parent class. Only the fields that have a matching id value should be set to + * the parent. We had to add the val.equals logic. + */ + if (val != null && parent != null && fieldType.getField().getType() == parent.getClass() + && val.equals(parentId)) { + fieldType.assignField(instance, parent, true, objectCache); + } else { + fieldType.assignField(instance, val, false, objectCache); + } + if (fieldType == idField) { + id = val; + } + } + } + if (foreignCollections) { + // go back and initialize any foreign collections + for (FieldType fieldType : resultsFieldTypes) { + if (fieldType.isForeignCollection()) { + BaseForeignCollection collection = fieldType.buildForeignCollection(instance, id); + if (collection != null) { + fieldType.assignField(instance, collection, false, objectCache); + } + } + } + } + // if we have a cache and we have an id then add it to the cache + if (objectCache != null && id != null) { + objectCache.put(clazz, id, instance); + } + if (columnPositions == null) { + columnPositions = colPosMap; + } + return instance; + } + + /** + * If we have a foreign collection object then this sets the value on the foreign object in the class. + */ + public void setParentInformation(Object parent, Object parentId) { + this.parent = parent; + this.parentId = parentId; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedStatement.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedStatement.java new file mode 100644 index 0000000..dd26c1a --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/BaseMappedStatement.java @@ -0,0 +1,89 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.table.TableInfo; + +/** + * Abstract mapped statement which has common statements used by the subclasses. + * + * @author graywatson + */ +public abstract class BaseMappedStatement { + + protected static Logger logger = LoggerFactory.getLogger(BaseMappedStatement.class); + + protected final TableInfo tableInfo; + protected final Class clazz; + protected final FieldType idField; + protected final String statement; + protected final FieldType[] argFieldTypes; + + protected BaseMappedStatement(TableInfo tableInfo, String statement, FieldType[] argFieldTypes) { + this.tableInfo = tableInfo; + this.clazz = tableInfo.getDataClass(); + this.idField = tableInfo.getIdField(); + this.statement = statement; + this.argFieldTypes = argFieldTypes; + } + + /** + * Return the array of field objects pulled from the data object. + */ + protected Object[] getFieldObjects(Object data) throws SQLException { + Object[] objects = new Object[argFieldTypes.length]; + for (int i = 0; i < argFieldTypes.length; i++) { + FieldType fieldType = argFieldTypes[i]; + if (fieldType.isAllowGeneratedIdInsert()) { + objects[i] = fieldType.getFieldValueIfNotDefault(data); + } else { + objects[i] = fieldType.extractJavaFieldToSqlArgValue(data); + } + if (objects[i] == null && fieldType.getDefaultValue() != null) { + objects[i] = fieldType.getDefaultValue(); + } + } + return objects; + } + + /** + * Return a field object converted from an id. + */ + protected Object convertIdToFieldObject(ID id) throws SQLException { + return idField.convertJavaFieldToSqlArgValue(id); + } + + static void appendWhereFieldEq(DatabaseType databaseType, FieldType fieldType, StringBuilder sb, + List fieldTypeList) { + sb.append("WHERE "); + appendFieldColumnName(databaseType, sb, fieldType, fieldTypeList); + sb.append("= ?"); + } + + static void appendTableName(DatabaseType databaseType, StringBuilder sb, String prefix, String tableName) { + if (prefix != null) { + sb.append(prefix); + } + databaseType.appendEscapedEntityName(sb, tableName); + sb.append(' '); + } + + static void appendFieldColumnName(DatabaseType databaseType, StringBuilder sb, FieldType fieldType, + List fieldTypeList) { + databaseType.appendEscapedEntityName(sb, fieldType.getColumnName()); + if (fieldTypeList != null) { + fieldTypeList.add(fieldType); + } + sb.append(' '); + } + + @Override + public String toString() { + return "MappedStatement: " + statement; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedCreate.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedCreate.java new file mode 100644 index 0000000..44c067b --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedCreate.java @@ -0,0 +1,272 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.logger.Log.Level; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.GeneratedKeyHolder; +import com.j256.ormlite.table.TableInfo; + +/** + * A mapped statement for creating a new instance of an object. + * + * @author graywatson + */ +public class MappedCreate extends BaseMappedStatement { + + private final String queryNextSequenceStmt; + private String dataClassName; + private int versionFieldTypeIndex; + + private MappedCreate(TableInfo tableInfo, String statement, FieldType[] argFieldTypes, + String queryNextSequenceStmt, int versionFieldTypeIndex) { + super(tableInfo, statement, argFieldTypes); + this.dataClassName = tableInfo.getDataClass().getSimpleName(); + this.queryNextSequenceStmt = queryNextSequenceStmt; + this.versionFieldTypeIndex = versionFieldTypeIndex; + } + + /** + * Create an object in the database. + */ + public int insert(DatabaseType databaseType, DatabaseConnection databaseConnection, T data, ObjectCache objectCache) + throws SQLException { + KeyHolder keyHolder = null; + if (idField != null) { + boolean assignId; + if (idField.isAllowGeneratedIdInsert() && !idField.isObjectsFieldValueDefault(data)) { + assignId = false; + } else { + assignId = true; + } + if (idField.isSelfGeneratedId() && idField.isGeneratedId()) { + if (assignId) { + idField.assignField(data, idField.generateId(), false, objectCache); + } + } else if (idField.isGeneratedIdSequence() && databaseType.isSelectSequenceBeforeInsert()) { + if (assignId) { + assignSequenceId(databaseConnection, data, objectCache); + } + } else if (idField.isGeneratedId()) { + if (assignId) { + // get the id back from the database + keyHolder = new KeyHolder(); + } + } else { + // the id should have been set by the caller already + } + } + + try { + // implement {@link DatabaseField#foreignAutoCreate()}, need to do this _before_ getFieldObjects() below + if (tableInfo.isForeignAutoCreate()) { + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (!fieldType.isForeignAutoCreate()) { + continue; + } + // get the field value + Object foreignObj = fieldType.extractRawJavaFieldValue(data); + if (foreignObj != null && fieldType.getForeignIdField().isObjectsFieldValueDefault(foreignObj)) { + fieldType.createWithForeignDao(foreignObj); + } + } + } + + Object[] args = getFieldObjects(data); + Object versionDefaultValue = null; + // implement {@link DatabaseField#version()} + if (versionFieldTypeIndex >= 0 && args[versionFieldTypeIndex] == null) { + // if the version is null then we need to initialize it before create + FieldType versionFieldType = argFieldTypes[versionFieldTypeIndex]; + versionDefaultValue = versionFieldType.moveToNextValue(null); + args[versionFieldTypeIndex] = versionFieldType.convertJavaFieldToSqlArgValue(versionDefaultValue); + } + + int rowC; + try { + rowC = databaseConnection.insert(statement, args, argFieldTypes, keyHolder); + } catch (SQLException e) { + logger.debug("insert data with statement '{}' and {} args, threw exception: {}", statement, + args.length, e); + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("insert arguments: {}", (Object) args); + } + throw e; + } + logger.debug("insert data with statement '{}' and {} args, changed {} rows", statement, args.length, rowC); + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("insert arguments: {}", (Object) args); + } + if (rowC > 0) { + if (versionDefaultValue != null) { + argFieldTypes[versionFieldTypeIndex].assignField(data, versionDefaultValue, false, null); + } + if (keyHolder != null) { + // assign the key returned by the database to the object's id field after it was inserted + Number key = keyHolder.getKey(); + if (key == null) { + // may never happen but let's be careful out there + throw new SQLException( + "generated-id key was not set by the update call, maybe a schema mismatch between entity and database table?"); + } + if (key.longValue() == 0L) { + // sanity check because the generated-key returned is 0 by default, may never happen + throw new SQLException( + "generated-id key must not be 0 value, maybe a schema mismatch between entity and database table?"); + } + assignIdValue(data, key, "keyholder", objectCache); + } + /* + * If we have a cache and if all of the foreign-collection fields have been assigned then add to cache. + * However, if one of the foreign collections has not be assigned then don't add it to the cache. + */ + if (objectCache != null && foreignCollectionsAreAssigned(tableInfo.getForeignCollections(), data)) { + Object id = idField.extractJavaFieldValue(data); + objectCache.put(clazz, id, data); + } + } + + return rowC; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Unable to run insert stmt on object " + data + ": " + statement, e); + } + } + + public static MappedCreate build(DatabaseType databaseType, TableInfo tableInfo) { + StringBuilder sb = new StringBuilder(128); + appendTableName(databaseType, sb, "INSERT INTO ", tableInfo.getTableName()); + int argFieldC = 0; + int versionFieldTypeIndex = -1; + // first we count up how many arguments we are going to have + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (isFieldCreatable(databaseType, fieldType)) { + if (fieldType.isVersion()) { + versionFieldTypeIndex = argFieldC; + } + argFieldC++; + } + } + FieldType[] argFieldTypes = new FieldType[argFieldC]; + if (argFieldC == 0) { + databaseType.appendInsertNoColumns(sb); + } else { + argFieldC = 0; + boolean first = true; + sb.append('('); + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (!isFieldCreatable(databaseType, fieldType)) { + continue; + } + if (first) { + first = false; + } else { + sb.append(","); + } + appendFieldColumnName(databaseType, sb, fieldType, null); + argFieldTypes[argFieldC++] = fieldType; + } + sb.append(") VALUES ("); + first = true; + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (!isFieldCreatable(databaseType, fieldType)) { + continue; + } + if (first) { + first = false; + } else { + sb.append(","); + } + sb.append("?"); + } + sb.append(")"); + } + FieldType idField = tableInfo.getIdField(); + String queryNext = buildQueryNextSequence(databaseType, idField); + return new MappedCreate(tableInfo, sb.toString(), argFieldTypes, queryNext, versionFieldTypeIndex); + } + + private boolean foreignCollectionsAreAssigned(FieldType[] foreignCollections, Object data) throws SQLException { + for (FieldType fieldType : foreignCollections) { + if (fieldType.extractJavaFieldValue(data) == null) { + return false; + } + } + return true; + } + + private static boolean isFieldCreatable(DatabaseType databaseType, FieldType fieldType) { + // we don't insert anything if it is a collection + if (fieldType.isForeignCollection()) { + // skip foreign collections + return false; + } else if (fieldType.isReadOnly()) { + // ignore read-only fields + return false; + } else if (databaseType.isIdSequenceNeeded() && databaseType.isSelectSequenceBeforeInsert()) { + // we need to query for the next value from the sequence and the idField is inserted afterwards + return true; + } else if (fieldType.isGeneratedId() && !fieldType.isSelfGeneratedId() && !fieldType.isAllowGeneratedIdInsert()) { + // skip generated-id fields because they will be auto-inserted + return false; + } else { + return true; + } + } + + private static String buildQueryNextSequence(DatabaseType databaseType, FieldType idField) { + if (idField == null) { + return null; + } + String seqName = idField.getGeneratedIdSequence(); + if (seqName == null) { + return null; + } else { + StringBuilder sb = new StringBuilder(64); + databaseType.appendSelectNextValFromSequence(sb, seqName); + return sb.toString(); + } + } + + private void assignSequenceId(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) + throws SQLException { + // call the query-next-sequence stmt to increment the sequence + long seqVal = databaseConnection.queryForLong(queryNextSequenceStmt); + logger.debug("queried for sequence {} using stmt: {}", seqVal, queryNextSequenceStmt); + if (seqVal == 0) { + // sanity check that it is working + throw new SQLException("Should not have returned 0 for stmt: " + queryNextSequenceStmt); + } + assignIdValue(data, seqVal, "sequence", objectCache); + } + + private void assignIdValue(T data, Number val, String label, ObjectCache objectCache) throws SQLException { + // better to do this in one please with consistent logging + idField.assignIdValue(data, val, objectCache); + if (logger.isLevelEnabled(Level.DEBUG)) { + logger.debug("assigned id '{}' from {} to '{}' in {} object", + new Object[] { val, label, idField.getFieldName(), dataClassName }); + } + } + + private static class KeyHolder implements GeneratedKeyHolder { + Number key; + + public Number getKey() { + return key; + } + + public void addKey(Number key) throws SQLException { + if (this.key == null) { + this.key = key; + } else { + throw new SQLException("generated key has already been set to " + this.key + ", now set to " + key); + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDelete.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDelete.java new file mode 100644 index 0000000..4c9a201 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDelete.java @@ -0,0 +1,78 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * A mapped statement for deleting an object. + * + * @author graywatson + */ +public class MappedDelete extends BaseMappedStatement { + + private MappedDelete(TableInfo tableInfo, String statement, FieldType[] argFieldTypes) { + super(tableInfo, statement, argFieldTypes); + } + + public static MappedDelete build(DatabaseType databaseType, TableInfo tableInfo) + throws SQLException { + FieldType idField = tableInfo.getIdField(); + if (idField == null) { + throw new SQLException("Cannot delete from " + tableInfo.getDataClass() + + " because it doesn't have an id field"); + } + StringBuilder sb = new StringBuilder(64); + appendTableName(databaseType, sb, "DELETE FROM ", tableInfo.getTableName()); + appendWhereFieldEq(databaseType, idField, sb, null); + return new MappedDelete(tableInfo, sb.toString(), new FieldType[] { idField }); + } + + /** + * Delete the object from the database. + */ + public int delete(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException { + try { + Object[] args = getFieldObjects(data); + int rowC = databaseConnection.delete(statement, args, argFieldTypes); + logger.debug("delete data with statement '{}' and {} args, changed {} rows", statement, args.length, rowC); + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("delete arguments: {}", (Object) args); + } + if (rowC > 0 && objectCache != null) { + Object id = idField.extractJavaFieldToSqlArgValue(data); + objectCache.remove(clazz, id); + } + return rowC; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Unable to run delete stmt on object " + data + ": " + statement, e); + } + } + + /** + * Delete the object from the database. + */ + public int deleteById(DatabaseConnection databaseConnection, ID id, ObjectCache objectCache) throws SQLException { + try { + Object[] args = new Object[] { convertIdToFieldObject(id) }; + int rowC = databaseConnection.delete(statement, args, argFieldTypes); + logger.debug("delete data with statement '{}' and {} args, changed {} rows", statement, args.length, rowC); + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("delete arguments: {}", (Object) args); + } + if (rowC > 0 && objectCache != null) { + objectCache.remove(clazz, id); + } + return rowC; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Unable to run deleteById stmt on id " + id + ": " + statement, e); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDeleteCollection.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDeleteCollection.java new file mode 100644 index 0000000..0e87ac0 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedDeleteCollection.java @@ -0,0 +1,117 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; +import java.util.Collection; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * A mapped statement for deleting objects that correspond to a collection of IDs. + * + * @author graywatson + */ +public class MappedDeleteCollection extends BaseMappedStatement { + + private MappedDeleteCollection(TableInfo tableInfo, String statement, FieldType[] argFieldTypes) { + super(tableInfo, statement, argFieldTypes); + } + + /** + * Delete all of the objects in the collection. This builds a {@link MappedDeleteCollection} on the fly because the + * datas could be variable sized. + */ + public static int deleteObjects(DatabaseType databaseType, TableInfo tableInfo, + DatabaseConnection databaseConnection, Collection datas, ObjectCache objectCache) throws SQLException { + MappedDeleteCollection deleteCollection = + MappedDeleteCollection.build(databaseType, tableInfo, datas.size()); + Object[] fieldObjects = new Object[datas.size()]; + FieldType idField = tableInfo.getIdField(); + int objC = 0; + for (T data : datas) { + fieldObjects[objC] = idField.extractJavaFieldToSqlArgValue(data); + objC++; + } + return updateRows(databaseConnection, tableInfo.getDataClass(), deleteCollection, fieldObjects, objectCache); + } + + /** + * Delete all of the objects in the collection. This builds a {@link MappedDeleteCollection} on the fly because the + * ids could be variable sized. + */ + public static int deleteIds(DatabaseType databaseType, TableInfo tableInfo, + DatabaseConnection databaseConnection, Collection ids, ObjectCache objectCache) throws SQLException { + MappedDeleteCollection deleteCollection = + MappedDeleteCollection.build(databaseType, tableInfo, ids.size()); + Object[] fieldObjects = new Object[ids.size()]; + FieldType idField = tableInfo.getIdField(); + int objC = 0; + for (ID id : ids) { + fieldObjects[objC] = idField.convertJavaFieldToSqlArgValue(id); + objC++; + } + return updateRows(databaseConnection, tableInfo.getDataClass(), deleteCollection, fieldObjects, objectCache); + } + + /** + * This is private because the execute is the only method that should be called here. + */ + private static MappedDeleteCollection build(DatabaseType databaseType, TableInfo tableInfo, + int dataSize) throws SQLException { + FieldType idField = tableInfo.getIdField(); + if (idField == null) { + throw new SQLException("Cannot delete " + tableInfo.getDataClass() + + " because it doesn't have an id field defined"); + } + StringBuilder sb = new StringBuilder(128); + appendTableName(databaseType, sb, "DELETE FROM ", tableInfo.getTableName()); + FieldType[] argFieldTypes = new FieldType[dataSize]; + appendWhereIds(databaseType, idField, sb, dataSize, argFieldTypes); + return new MappedDeleteCollection(tableInfo, sb.toString(), argFieldTypes); + } + + private static int updateRows(DatabaseConnection databaseConnection, Class clazz, + MappedDeleteCollection deleteCollection, Object[] args, ObjectCache objectCache) throws SQLException { + try { + int rowC = databaseConnection.delete(deleteCollection.statement, args, deleteCollection.argFieldTypes); + if (rowC > 0 && objectCache != null) { + for (Object id : args) { + objectCache.remove(clazz, id); + } + } + logger.debug("delete-collection with statement '{}' and {} args, changed {} rows", + deleteCollection.statement, args.length, rowC); + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("delete-collection arguments: {}", (Object) args); + } + return rowC; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Unable to run delete collection stmt: " + deleteCollection.statement, e); + } + } + + private static void appendWhereIds(DatabaseType databaseType, FieldType idField, StringBuilder sb, int numDatas, + FieldType[] fieldTypes) { + sb.append("WHERE "); + databaseType.appendEscapedEntityName(sb, idField.getColumnName()); + sb.append(" IN ("); + boolean first = true; + for (int i = 0; i < numDatas; i++) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append('?'); + if (fieldTypes != null) { + fieldTypes[i] = idField; + } + } + sb.append(") "); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedPreparedStmt.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedPreparedStmt.java new file mode 100644 index 0000000..c20d17d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedPreparedStmt.java @@ -0,0 +1,119 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.field.SqlType; +import com.j256.ormlite.logger.Log.Level; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.PreparedDelete; +import com.j256.ormlite.stmt.PreparedQuery; +import com.j256.ormlite.stmt.PreparedUpdate; +import com.j256.ormlite.stmt.StatementBuilder; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * Mapped statement used by the {@link StatementBuilder#prepareStatement(Long)} method. + * + * @author graywatson + */ +public class MappedPreparedStmt extends BaseMappedQuery implements PreparedQuery, PreparedDelete, + PreparedUpdate { + + private final ArgumentHolder[] argHolders; + private final Long limit; + private final StatementType type; + + public MappedPreparedStmt(TableInfo tableInfo, String statement, FieldType[] argFieldTypes, + FieldType[] resultFieldTypes, ArgumentHolder[] argHolders, Long limit, StatementType type) { + super(tableInfo, statement, argFieldTypes, resultFieldTypes); + this.argHolders = argHolders; + // this is an Integer because it may be null + this.limit = limit; + this.type = type; + } + + public CompiledStatement compile(DatabaseConnection databaseConnection, StatementType type) throws SQLException { + return compile(databaseConnection, type, DatabaseConnection.DEFAULT_RESULT_FLAGS); + } + + public CompiledStatement compile(DatabaseConnection databaseConnection, StatementType type, int resultFlags) + throws SQLException { + if (this.type != type) { + throw new SQLException("Could not compile this " + this.type + + " statement since the caller is expecting a " + type + + " statement. Check your QueryBuilder methods."); + } + CompiledStatement stmt = databaseConnection.compileStatement(statement, type, argFieldTypes, resultFlags); + // this may return null if the stmt had to be closed + return assignStatementArguments(stmt); + } + + public String getStatement() { + return statement; + } + + public StatementType getType() { + return type; + } + + public void setArgumentHolderValue(int index, Object value) throws SQLException { + if (index < 0) { + throw new SQLException("argument holder index " + index + " must be >= 0"); + } + if (argHolders.length <= index) { + throw new SQLException("argument holder index " + index + " is not valid, only " + argHolders.length + + " in statement (index starts at 0)"); + } + argHolders[index].setValue(value); + } + + /** + * Assign arguments to the statement. + * + * @return The statement passed in or null if it had to be closed on error. + */ + private CompiledStatement assignStatementArguments(CompiledStatement stmt) throws SQLException { + boolean ok = false; + try { + if (limit != null) { + // we use this if SQL statement LIMITs are not supported by this database type + stmt.setMaxRows(limit.intValue()); + } + // set any arguments if we are logging our object + Object[] argValues = null; + if (logger.isLevelEnabled(Level.TRACE) && argHolders.length > 0) { + argValues = new Object[argHolders.length]; + } + for (int i = 0; i < argHolders.length; i++) { + Object argValue = argHolders[i].getSqlArgValue(); + FieldType fieldType = argFieldTypes[i]; + SqlType sqlType; + if (fieldType == null) { + sqlType = argHolders[i].getSqlType(); + } else { + sqlType = fieldType.getSqlType(); + } + stmt.setObject(i, argValue, sqlType); + if (argValues != null) { + argValues[i] = argValue; + } + } + logger.debug("prepared statement '{}' with {} args", statement, argHolders.length); + if (argValues != null) { + // need to do the (Object) cast to force args to be a single object + logger.trace("prepared statement arguments: {}", (Object) argValues); + } + ok = true; + return stmt; + } finally { + if (!ok) { + IOUtils.closeThrowSqlException(stmt, "statement"); + } + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedQueryForId.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedQueryForId.java new file mode 100644 index 0000000..7e616db --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedQueryForId.java @@ -0,0 +1,83 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * Mapped statement for querying for an object by its ID. + * + * @author graywatson + */ +public class MappedQueryForId extends BaseMappedQuery { + + private final String label; + + protected MappedQueryForId(TableInfo tableInfo, String statement, FieldType[] argFieldTypes, + FieldType[] resultsFieldTypes, String label) { + super(tableInfo, statement, argFieldTypes, resultsFieldTypes); + this.label = label; + } + + /** + * Query for an object in the database which matches the id argument. + */ + public T execute(DatabaseConnection databaseConnection, ID id, ObjectCache objectCache) throws SQLException { + if (objectCache != null) { + T result = objectCache.get(clazz, id); + if (result != null) { + return result; + } + } + Object[] args = new Object[] { convertIdToFieldObject(id) }; + // @SuppressWarnings("unchecked") + Object result = databaseConnection.queryForOne(statement, args, argFieldTypes, this, objectCache); + if (result == null) { + logger.debug("{} using '{}' and {} args, got no results", label, statement, args.length); + } else if (result == DatabaseConnection.MORE_THAN_ONE) { + logger.error("{} using '{}' and {} args, got >1 results", label, statement, args.length); + logArgs(args); + throw new SQLException(label + " got more than 1 result: " + statement); + } else { + logger.debug("{} using '{}' and {} args, got 1 result", label, statement, args.length); + } + logArgs(args); + @SuppressWarnings("unchecked") + T castResult = (T) result; + return castResult; + } + + public static MappedQueryForId build(DatabaseType databaseType, TableInfo tableInfo, + FieldType idFieldType) throws SQLException { + if (idFieldType == null) { + idFieldType = tableInfo.getIdField(); + if (idFieldType == null) { + throw new SQLException("Cannot query-for-id with " + tableInfo.getDataClass() + + " because it doesn't have an id field"); + } + } + String statement = buildStatement(databaseType, tableInfo, idFieldType); + return new MappedQueryForId(tableInfo, statement, new FieldType[] { idFieldType }, + tableInfo.getFieldTypes(), "query-for-id"); + } + + protected static String buildStatement(DatabaseType databaseType, TableInfo tableInfo, + FieldType idFieldType) { + // build the select statement by hand + StringBuilder sb = new StringBuilder(64); + appendTableName(databaseType, sb, "SELECT * FROM ", tableInfo.getTableName()); + appendWhereFieldEq(databaseType, idFieldType, sb, null); + return sb.toString(); + } + + private void logArgs(Object[] args) { + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object and not an array + logger.trace("{} arguments: {}", label, (Object) args); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedRefresh.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedRefresh.java new file mode 100644 index 0000000..58290ed --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedRefresh.java @@ -0,0 +1,57 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * Mapped statement for refreshing the fields in an object. + * + * @author graywatson + */ +public class MappedRefresh extends MappedQueryForId { + + private MappedRefresh(TableInfo tableInfo, String statement, FieldType[] argFieldTypes, + FieldType[] resultFieldTypes) { + super(tableInfo, statement, argFieldTypes, resultFieldTypes, "refresh"); + } + + /** + * Execute our refresh query statement and then update all of the fields in data with the fields from the result. + * + * @return 1 if we found the object in the table by id or 0 if not. + */ + public int executeRefresh(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) + throws SQLException { + @SuppressWarnings("unchecked") + ID id = (ID) idField.extractJavaFieldValue(data); + // we don't care about the cache here + T result = super.execute(databaseConnection, id, null); + if (result == null) { + return 0; + } + // copy each field from the result into the passed in object + for (FieldType fieldType : resultsFieldTypes) { + if (fieldType != idField) { + fieldType.assignField(data, fieldType.extractJavaFieldValue(result), false, objectCache); + } + } + return 1; + } + + public static MappedRefresh build(DatabaseType databaseType, TableInfo tableInfo) + throws SQLException { + FieldType idField = tableInfo.getIdField(); + if (idField == null) { + throw new SQLException("Cannot refresh " + tableInfo.getDataClass() + + " because it doesn't have an id field"); + } + String statement = buildStatement(databaseType, tableInfo, idField); + return new MappedRefresh(tableInfo, statement, new FieldType[] { tableInfo.getIdField() }, + tableInfo.getFieldTypes()); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdate.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdate.java new file mode 100644 index 0000000..eaec460 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdate.java @@ -0,0 +1,140 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * Mapped statement for updating an object. + * + * @author graywatson + */ +public class MappedUpdate extends BaseMappedStatement { + + private final FieldType versionFieldType; + private final int versionFieldTypeIndex; + + private MappedUpdate(TableInfo tableInfo, String statement, FieldType[] argFieldTypes, + FieldType versionFieldType, int versionFieldTypeIndex) { + super(tableInfo, statement, argFieldTypes); + this.versionFieldType = versionFieldType; + this.versionFieldTypeIndex = versionFieldTypeIndex; + } + + public static MappedUpdate build(DatabaseType databaseType, TableInfo tableInfo) + throws SQLException { + FieldType idField = tableInfo.getIdField(); + if (idField == null) { + throw new SQLException("Cannot update " + tableInfo.getDataClass() + " because it doesn't have an id field"); + } + StringBuilder sb = new StringBuilder(64); + appendTableName(databaseType, sb, "UPDATE ", tableInfo.getTableName()); + boolean first = true; + int argFieldC = 0; + FieldType versionFieldType = null; + int versionFieldTypeIndex = -1; + // first we count up how many arguments we are going to have + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (isFieldUpdatable(fieldType, idField)) { + if (fieldType.isVersion()) { + versionFieldType = fieldType; + versionFieldTypeIndex = argFieldC; + } + argFieldC++; + } + } + // one more for where id = ? + argFieldC++; + if (versionFieldType != null) { + // one more for the AND version = ? + argFieldC++; + } + FieldType[] argFieldTypes = new FieldType[argFieldC]; + argFieldC = 0; + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (!isFieldUpdatable(fieldType, idField)) { + continue; + } + if (first) { + sb.append("SET "); + first = false; + } else { + sb.append(", "); + } + appendFieldColumnName(databaseType, sb, fieldType, null); + argFieldTypes[argFieldC++] = fieldType; + sb.append("= ?"); + } + sb.append(' '); + appendWhereFieldEq(databaseType, idField, sb, null); + argFieldTypes[argFieldC++] = idField; + if (versionFieldType != null) { + sb.append(" AND "); + appendFieldColumnName(databaseType, sb, versionFieldType, null); + sb.append("= ?"); + argFieldTypes[argFieldC++] = versionFieldType; + } + return new MappedUpdate(tableInfo, sb.toString(), argFieldTypes, versionFieldType, versionFieldTypeIndex); + } + + /** + * Update the object in the database. + */ + public int update(DatabaseConnection databaseConnection, T data, ObjectCache objectCache) throws SQLException { + try { + // there is always and id field as an argument so just return 0 lines updated + if (argFieldTypes.length <= 1) { + return 0; + } + Object[] args = getFieldObjects(data); + Object newVersion = null; + if (versionFieldType != null) { + newVersion = versionFieldType.extractJavaFieldValue(data); + newVersion = versionFieldType.moveToNextValue(newVersion); + args[versionFieldTypeIndex] = versionFieldType.convertJavaFieldToSqlArgValue(newVersion); + } + int rowC = databaseConnection.update(statement, args, argFieldTypes); + if (rowC > 0) { + if (newVersion != null) { + // if we have updated a row then update the version field in our object to the new value + versionFieldType.assignField(data, newVersion, false, null); + } + if (objectCache != null) { + // if we've changed something then see if we need to update our cache + Object id = idField.extractJavaFieldValue(data); + T cachedData = objectCache.get(clazz, id); + if (cachedData != null && cachedData != data) { + // copy each field from the updated data into the cached object + for (FieldType fieldType : tableInfo.getFieldTypes()) { + if (fieldType != idField) { + fieldType.assignField(cachedData, fieldType.extractJavaFieldValue(data), false, + objectCache); + } + } + } + } + } + logger.debug("update data with statement '{}' and {} args, changed {} rows", statement, args.length, rowC); + if (args.length > 0) { + // need to do the (Object) cast to force args to be a single object + logger.trace("update arguments: {}", (Object) args); + } + return rowC; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Unable to run update stmt on object " + data + ": " + statement, e); + } + } + + private static boolean isFieldUpdatable(FieldType fieldType, FieldType idField) { + if (fieldType == idField || fieldType.isForeignCollection() || fieldType.isReadOnly()) { + return false; + } else { + return true; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdateId.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdateId.java new file mode 100644 index 0000000..9bbc05e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/mapped/MappedUpdateId.java @@ -0,0 +1,77 @@ +package com.j256.ormlite.stmt.mapped; + +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableInfo; + +/** + * Mapped statement for updating an object's ID field. + * + * @author graywatson + */ +public class MappedUpdateId extends BaseMappedStatement { + + private MappedUpdateId(TableInfo tableInfo, String statement, FieldType[] argFieldTypes) { + super(tableInfo, statement, argFieldTypes); + } + + /** + * Update the id field of the object in the database. + */ + public int execute(DatabaseConnection databaseConnection, T data, ID newId, ObjectCache objectCache) + throws SQLException { + try { + // the arguments are the new-id and old-id + Object[] args = new Object[] { convertIdToFieldObject(newId), extractIdToFieldObject(data) }; + int rowC = databaseConnection.update(statement, args, argFieldTypes); + if (rowC > 0) { + if (objectCache != null) { + Object oldId = idField.extractJavaFieldValue(data); + T obj = objectCache.updateId(clazz, oldId, newId); + if (obj != null && obj != data) { + // if our cached value is not the data that will be updated then we need to update it specially + idField.assignField(obj, newId, false, objectCache); + } + } + // adjust the object to assign the new id + idField.assignField(data, newId, false, objectCache); + } + logger.debug("updating-id with statement '{}' and {} args, changed {} rows", statement, args.length, rowC); + if (args.length > 0) { + // need to do the cast otherwise we only print the first object in args + logger.trace("updating-id arguments: {}", (Object) args); + } + return rowC; + } catch (SQLException e) { + throw SqlExceptionUtil.create("Unable to run update-id stmt on object " + data + ": " + statement, e); + } + } + + public static MappedUpdateId build(DatabaseType databaseType, TableInfo tableInfo) + throws SQLException { + FieldType idField = tableInfo.getIdField(); + if (idField == null) { + throw new SQLException("Cannot update-id in " + tableInfo.getDataClass() + + " because it doesn't have an id field"); + } + StringBuilder sb = new StringBuilder(64); + appendTableName(databaseType, sb, "UPDATE ", tableInfo.getTableName()); + sb.append("SET "); + appendFieldColumnName(databaseType, sb, idField, null); + sb.append("= ? "); + appendWhereFieldEq(databaseType, idField, sb, null); + return new MappedUpdateId(tableInfo, sb.toString(), new FieldType[] { idField, idField }); + } + + /** + * Return a field-object for the id extracted from the data. + */ + private Object extractIdToFieldObject(T data) throws SQLException { + return idField.extractJavaFieldToSqlArgValue(data); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/BaseComparison.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/BaseComparison.java new file mode 100644 index 0000000..be3ef8d --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/BaseComparison.java @@ -0,0 +1,131 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.ColumnArg; +import com.j256.ormlite.stmt.SelectArg; + +/** + * Internal base class for all comparison operations. + * + * @author graywatson + */ +abstract class BaseComparison implements Comparison { + + private static final String NUMBER_CHARACTERS = "0123456789.-+"; + protected final String columnName; + protected final FieldType fieldType; + private final Object value; + + protected BaseComparison(String columnName, FieldType fieldType, Object value, boolean isComparison) + throws SQLException { + if (isComparison && fieldType != null && !fieldType.isComparable()) { + throw new SQLException("Field '" + columnName + "' is of data type " + fieldType.getDataPersister() + + " which can not be compared"); + } + this.columnName = columnName; + this.fieldType = fieldType; + this.value = value; + } + + public abstract void appendOperation(StringBuilder sb); + + public void appendSql(DatabaseType databaseType, String tableName, StringBuilder sb, List argList) + throws SQLException { + if (tableName != null) { + databaseType.appendEscapedEntityName(sb, tableName); + sb.append('.'); + } + databaseType.appendEscapedEntityName(sb, columnName); + sb.append(' '); + appendOperation(sb); + // this needs to call appendValue (not appendArgOrValue) because it may be overridden + appendValue(databaseType, sb, argList); + } + + public String getColumnName() { + return columnName; + } + + public void appendValue(DatabaseType databaseType, StringBuilder sb, List argList) + throws SQLException { + appendArgOrValue(databaseType, fieldType, sb, argList, value); + } + + /** + * Append to the string builder either a {@link ArgumentHolder} argument or a value object. + */ + protected void appendArgOrValue(DatabaseType databaseType, FieldType fieldType, StringBuilder sb, + List argList, Object argOrValue) throws SQLException { + boolean appendSpace = true; + if (argOrValue == null) { + throw new SQLException("argument for '" + fieldType.getFieldName() + "' is null"); + } else if (argOrValue instanceof ArgumentHolder) { + sb.append('?'); + ArgumentHolder argHolder = (ArgumentHolder) argOrValue; + argHolder.setMetaInfo(columnName, fieldType); + argList.add(argHolder); + } else if (argOrValue instanceof ColumnArg) { + ColumnArg columnArg = (ColumnArg) argOrValue; + String tableName = columnArg.getTableName(); + if (tableName != null) { + databaseType.appendEscapedEntityName(sb, tableName); + sb.append('.'); + } + databaseType.appendEscapedEntityName(sb, columnArg.getColumnName()); + } else if (fieldType.isArgumentHolderRequired()) { + sb.append('?'); + ArgumentHolder argHolder = new SelectArg(); + argHolder.setMetaInfo(columnName, fieldType); + // conversion is done when the getValue() is called + argHolder.setValue(argOrValue); + argList.add(argHolder); + } else if (fieldType.isForeign() && fieldType.getType().isAssignableFrom(argOrValue.getClass())) { + /* + * If we have a foreign field and our argument is an instance of the foreign object (i.e. not its id), then + * we need to extract the id. We allow super-classes of the field but not sub-classes. + */ + FieldType idFieldType = fieldType.getForeignIdField(); + appendArgOrValue(databaseType, idFieldType, sb, argList, idFieldType.extractJavaFieldValue(argOrValue)); + // no need for the space since it was done in the recursion + appendSpace = false; + } else if (fieldType.isEscapedValue()) { + databaseType.appendEscapedWord(sb, fieldType.convertJavaFieldToSqlArgValue(argOrValue).toString()); + } else if (fieldType.isForeign()) { + /* + * I'm not entirely sure this is correct. This is trying to protect against someone trying to pass an object + * into a comparison with a foreign field. Typically if they pass the same field type, then ORMLite will + * extract the ID of the foreign. + */ + String value = fieldType.convertJavaFieldToSqlArgValue(argOrValue).toString(); + if (value.length() > 0) { + if (NUMBER_CHARACTERS.indexOf(value.charAt(0)) < 0) { + throw new SQLException("Foreign field " + fieldType + + " does not seem to be producing a numerical value '" + value + + "'. Maybe you are passing the wrong object to comparison: " + this); + } + } + sb.append(value); + } else { + // numbers can't have quotes around them in derby + sb.append(fieldType.convertJavaFieldToSqlArgValue(argOrValue)); + } + if (appendSpace) { + sb.append(' '); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(columnName).append(' '); + appendOperation(sb); + sb.append(' '); + sb.append(value); + return sb.toString(); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Between.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Between.java new file mode 100644 index 0000000..c042e18 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Between.java @@ -0,0 +1,45 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'between' query part. Used by {@link Where#between}. + * + * @author graywatson + */ +public class Between extends BaseComparison { + + private Object low; + private Object high; + + public Between(String columnName, FieldType fieldType, Object low, Object high) throws SQLException { + super(columnName, fieldType, null, true); + this.low = low; + this.high = high; + } + + @Override + public void appendOperation(StringBuilder sb) { + sb.append("BETWEEN "); + } + + @Override + public void appendValue(DatabaseType databaseType, StringBuilder sb, List argList) + throws SQLException { + if (low == null) { + throw new IllegalArgumentException("BETWEEN low value for '" + columnName + "' is null"); + } + if (high == null) { + throw new IllegalArgumentException("BETWEEN high value for '" + columnName + "' is null"); + } + appendArgOrValue(databaseType, fieldType, sb, argList, low); + sb.append("AND "); + appendArgOrValue(databaseType, fieldType, sb, argList, high); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Clause.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Clause.java new file mode 100644 index 0000000..5760cb8 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Clause.java @@ -0,0 +1,24 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.stmt.ArgumentHolder; + +/** + * Internal marker class for query clauses. + * + * @author graywatson + */ +public interface Clause { + + /** + * Add to the string-builder the appropriate SQL for this clause. + * + * @param tableName + * Name of the table to prepend to any column names or null to be ignored. + */ + public void appendSql(DatabaseType databaseType, String tableName, StringBuilder sb, List argList) + throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ColumnNameOrRawSql.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ColumnNameOrRawSql.java new file mode 100644 index 0000000..a3b7660 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ColumnNameOrRawSql.java @@ -0,0 +1,42 @@ +package com.j256.ormlite.stmt.query; + +/** + * Internal class handling the SQL 'GROUP BY' operation and other lists of column-names or raw SQL. + * + * @author graywatson + */ +public class ColumnNameOrRawSql { + + private final String columnName; + private final String rawSql; + + public static ColumnNameOrRawSql withColumnName(String columnName) { + return new ColumnNameOrRawSql(columnName, null); + } + + public static ColumnNameOrRawSql withRawSql(String rawSql) { + return new ColumnNameOrRawSql(null, rawSql); + } + + private ColumnNameOrRawSql(String columnName, String rawSql) { + this.columnName = columnName; + this.rawSql = rawSql; + } + + public String getColumnName() { + return columnName; + } + + public String getRawSql() { + return rawSql; + } + + @Override + public String toString() { + if (rawSql == null) { + return columnName; + } else { + return rawSql; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Comparison.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Comparison.java new file mode 100644 index 0000000..6f12bce --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Comparison.java @@ -0,0 +1,31 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.stmt.ArgumentHolder; + +/** + * Internal interfaces which define a comparison operation. + * + * @author graywatson + */ +interface Comparison extends Clause { + + /** + * Return the column-name associated with the comparison. + */ + public String getColumnName(); + + /** + * Add the operation used in this comparison to the string builder. + */ + public void appendOperation(StringBuilder sb); + + /** + * Add the value of the comparison to the string builder. + */ + public void appendValue(DatabaseType databaseType, StringBuilder sb, List argList) + throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Exists.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Exists.java new file mode 100644 index 0000000..84ed667 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Exists.java @@ -0,0 +1,30 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.QueryBuilder.InternalQueryBuilderWrapper; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'EXISTS' query part. Used by {@link Where#exists}. + * + * @author graywatson + */ +public class Exists implements Clause { + + private final InternalQueryBuilderWrapper subQueryBuilder; + + public Exists(InternalQueryBuilderWrapper subQueryBuilder) { + this.subQueryBuilder = subQueryBuilder; + } + + public void appendSql(DatabaseType databaseType, String tableName, StringBuilder sb, List argList) + throws SQLException { + sb.append("EXISTS ("); + subQueryBuilder.appendStatementString(sb, argList); + sb.append(") "); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/In.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/In.java new file mode 100644 index 0000000..2cdccfa --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/In.java @@ -0,0 +1,63 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'in' query part. Used by {@link Where#in}. + * + * @author graywatson + */ +public class In extends BaseComparison { + + private Iterable objects; + private final boolean in; + + public In(String columnName, FieldType fieldType, Iterable objects, boolean in) throws SQLException { + super(columnName, fieldType, null, true); + this.objects = objects; + this.in = in; + } + + public In(String columnName, FieldType fieldType, Object[] objects, boolean in) throws SQLException { + super(columnName, fieldType, null, true); + // grrrr, Object[] should be Iterable + this.objects = Arrays.asList(objects); + this.in = in; + } + + @Override + public void appendOperation(StringBuilder sb) { + if (in) { + sb.append("IN "); + } else { + sb.append("NOT IN "); + } + } + + @Override + public void appendValue(DatabaseType databaseType, StringBuilder sb, List columnArgList) + throws SQLException { + sb.append('('); + boolean first = true; + for (Object value : objects) { + if (value == null) { + throw new IllegalArgumentException("one of the IN values for '" + columnName + "' is null"); + } + if (first) { + first = false; + } else { + sb.append(','); + } + // for each of our arguments, add it to the output + super.appendArgOrValue(databaseType, fieldType, sb, columnArgList, value); + } + sb.append(") "); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/InSubQuery.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/InSubQuery.java new file mode 100644 index 0000000..f2291d0 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/InSubQuery.java @@ -0,0 +1,55 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.QueryBuilder.InternalQueryBuilderWrapper; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'in' query part. Used by {@link Where#in}. + * + * @author graywatson + */ +public class InSubQuery extends BaseComparison { + + private final InternalQueryBuilderWrapper subQueryBuilder; + private final boolean in; + + public InSubQuery(String columnName, FieldType fieldType, InternalQueryBuilderWrapper subQueryBuilder, boolean in) + throws SQLException { + super(columnName, fieldType, null, true); + this.subQueryBuilder = subQueryBuilder; + this.in = in; + } + + @Override + public void appendOperation(StringBuilder sb) { + if (in) { + sb.append("IN "); + } else { + sb.append("NOT IN "); + } + } + + @Override + public void appendValue(DatabaseType databaseType, StringBuilder sb, List argList) + throws SQLException { + sb.append('('); + subQueryBuilder.appendStatementString(sb, argList); + FieldType[] resultFieldTypes = subQueryBuilder.getResultFieldTypes(); + if (resultFieldTypes == null) { + // we assume that if someone is doing a raw select, they know what they are doing + } else if (resultFieldTypes.length != 1) { + throw new SQLException("There must be only 1 result column in sub-query but we found " + + resultFieldTypes.length); + } else if (fieldType.getSqlType() != resultFieldTypes[0].getSqlType()) { + throw new SQLException("Outer column " + fieldType + " is not the same type as inner column " + + resultFieldTypes[0]); + } + sb.append(") "); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNotNull.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNotNull.java new file mode 100644 index 0000000..3cc6fba --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNotNull.java @@ -0,0 +1,31 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'IS NOT NULL' comparison query part. Used by {@link Where#isNull}. + * + * @author graywatson + */ +public class IsNotNull extends BaseComparison { + + public IsNotNull(String columnName, FieldType fieldType) throws SQLException { + super(columnName, fieldType, null, false); + } + + @Override + public void appendOperation(StringBuilder sb) { + sb.append("IS NOT NULL "); + } + + @Override + public void appendValue(DatabaseType databaseType, StringBuilder sb, List argList) { + // there is no value + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNull.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNull.java new file mode 100644 index 0000000..e97633b --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/IsNull.java @@ -0,0 +1,31 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'IS NULL' comparison query part. Used by {@link Where#isNull}. + * + * @author graywatson + */ +public class IsNull extends BaseComparison { + + public IsNull(String columnName, FieldType fieldType) throws SQLException { + super(columnName, fieldType, null, false); + } + + @Override + public void appendOperation(StringBuilder sb) { + sb.append("IS NULL "); + } + + @Override + public void appendValue(DatabaseType databaseType, StringBuilder sb, List argList) { + // there is no value + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ManyClause.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ManyClause.java new file mode 100644 index 0000000..b909ff9 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/ManyClause.java @@ -0,0 +1,77 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.stmt.ArgumentHolder; + +/** + * For operations with a number of them in a row. + * + * @author graywatson + */ +public class ManyClause implements Clause, NeedsFutureClause { + + public static final String AND_OPERATION = "AND"; + public static final String OR_OPERATION = "OR"; + + private final Clause first; + private Clause second; + private final Clause[] others; + private final int startOthersAt; + private final String operation; + + public ManyClause(Clause first, String operation) { + this.first = first; + // second will be set later + this.second = null; + this.others = null; + this.startOthersAt = 0; + this.operation = operation; + } + + public ManyClause(Clause first, Clause second, Clause[] others, String operation) { + this.first = first; + this.second = second; + this.others = others; + this.startOthersAt = 0; + this.operation = operation; + } + + public ManyClause(Clause[] others, String operation) { + this.first = others[0]; + if (others.length < 2) { + this.second = null; + this.startOthersAt = others.length; + } else { + this.second = others[1]; + this.startOthersAt = 2; + } + this.others = others; + this.operation = operation; + } + + public void appendSql(DatabaseType databaseType, String tableName, StringBuilder sb, + List selectArgList) throws SQLException { + sb.append("("); + first.appendSql(databaseType, tableName, sb, selectArgList); + if (second != null) { + sb.append(operation); + sb.append(' '); + second.appendSql(databaseType, tableName, sb, selectArgList); + } + if (others != null) { + for (int i = startOthersAt; i < others.length; i++) { + sb.append(operation); + sb.append(' '); + others[i].appendSql(databaseType, tableName, sb, selectArgList); + } + } + sb.append(") "); + } + + public void setMissingClause(Clause right) { + second = right; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/NeedsFutureClause.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/NeedsFutureClause.java new file mode 100644 index 0000000..d788aba --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/NeedsFutureClause.java @@ -0,0 +1,31 @@ +package com.j256.ormlite.stmt.query; + +/** + *

+ * Internal interface which defines a clause that consumes a future clause. This allows us to do: + *

+ * + *
+ * where.not();
+ * where.eq("id", 1234);
+ * 
+ * + *

+ * and + *

+ * + *
+ * where.eq("id", 1234);
+ * where.and();
+ * where.gt("age", 44);
+ * 
+ * + * @author graywatson + */ +public interface NeedsFutureClause extends Clause { + + /** + * Set the right side of the binary operation. + */ + public void setMissingClause(Clause right); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Not.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Not.java new file mode 100644 index 0000000..dc94753 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Not.java @@ -0,0 +1,79 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.Where; + +/** + * Internal class handling the SQL 'NOT' boolean comparison operation. Used by {@link Where#not}. + * + * @author graywatson + */ +public class Not implements Clause, NeedsFutureClause { + + private Comparison comparison = null; + private Exists exists = null; + + /** + * In this case we will consume a future clause. + */ + public Not() { + } + + /** + * Create a Not from a {@link Clause}. + * + * @throws IllegalArgumentException + * If the {@link Clause} is not a {@link Comparison}. + */ + public Not(Clause clause) { + setMissingClause(clause); + } + + public void setMissingClause(Clause clause) { + if (this.comparison != null) { + throw new IllegalArgumentException("NOT operation already has a comparison set"); + } else if (clause instanceof Comparison) { + this.comparison = (Comparison) clause; + } else if (clause instanceof Exists) { + this.exists = (Exists) clause; + } else { + throw new IllegalArgumentException("NOT operation can only work with comparison SQL clauses, not " + clause); + } + } + + public void appendSql(DatabaseType databaseType, String tableName, StringBuilder sb, + List selectArgList) throws SQLException { + if (comparison == null && exists == null) { + throw new IllegalStateException("Clause has not been set in NOT operation"); + } + // this generates: (NOT 'x' = 123 ) + if (comparison == null) { + sb.append("(NOT "); + exists.appendSql(databaseType, tableName, sb, selectArgList); + } else { + sb.append("(NOT "); + if (tableName != null) { + databaseType.appendEscapedEntityName(sb, tableName); + sb.append('.'); + } + databaseType.appendEscapedEntityName(sb, comparison.getColumnName()); + sb.append(' '); + comparison.appendOperation(sb); + comparison.appendValue(databaseType, sb, selectArgList); + } + sb.append(") "); + } + + @Override + public String toString() { + if (comparison == null) { + return "NOT without comparison"; + } else { + return "NOT comparison " + comparison; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/OrderBy.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/OrderBy.java new file mode 100644 index 0000000..d911d6b --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/OrderBy.java @@ -0,0 +1,48 @@ +package com.j256.ormlite.stmt.query; + +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.QueryBuilder; + +/** + * Internal class handling the SQL 'ORDER BY' operation. Used by {@link QueryBuilder#orderBy(String, boolean)} and + * {@link QueryBuilder#orderByRaw(String)}. + * + * @author graywatson + */ +public class OrderBy { + + private final String columnName; + private final boolean ascending; + private final String rawSql; + private final ArgumentHolder[] orderByArgs; + + public OrderBy(String columnName, boolean ascending) { + this.columnName = columnName; + this.ascending = ascending; + this.rawSql = null; + this.orderByArgs = null; + } + + public OrderBy(String rawSql, ArgumentHolder[] orderByArgs) { + this.columnName = null; + this.ascending = true; + this.rawSql = rawSql; + this.orderByArgs = orderByArgs; + } + + public String getColumnName() { + return columnName; + } + + public boolean isAscending() { + return ascending; + } + + public String getRawSql() { + return rawSql; + } + + public ArgumentHolder[] getOrderByArgs() { + return orderByArgs; + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Raw.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Raw.java new file mode 100644 index 0000000..df3a3f5 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/Raw.java @@ -0,0 +1,30 @@ +package com.j256.ormlite.stmt.query; + +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.stmt.ArgumentHolder; + +/** + * Raw part of the where to just stick in a string in the middle of the WHERE. It is up to the user to do so properly. + * + * @author graywatson + */ +public class Raw implements Clause { + + private final String statement; + private final ArgumentHolder[] args; + + public Raw(String statement, ArgumentHolder[] args) { + this.statement = statement; + this.args = args; + } + + public void appendSql(DatabaseType databaseType, String tableName, StringBuilder sb, List argList) { + sb.append(statement); + sb.append(' '); + for (ArgumentHolder arg : args) { + argList.add(arg); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetExpression.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetExpression.java new file mode 100644 index 0000000..534a612 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetExpression.java @@ -0,0 +1,38 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.StatementBuilder; + +/** + * Internal class handling the SQL SET part used by UPDATE statements. Used by + * {@link StatementBuilder#updateColumnExpression(String, String)}. + * + *

+ * It's not a comparison per se but does have a columnName = value form so it works. + *

+ * + * @author graywatson + */ +public class SetExpression extends BaseComparison { + + public SetExpression(String columnName, FieldType fieldType, String string) throws SQLException { + super(columnName, fieldType, string, true); + } + + @Override + public void appendOperation(StringBuilder sb) { + sb.append("= "); + } + + @Override + protected void appendArgOrValue(DatabaseType databaseType, FieldType fieldType, StringBuilder sb, + List selectArgList, Object argOrValue) { + // we know it is a string so just append it + sb.append(argOrValue).append(' '); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetValue.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetValue.java new file mode 100644 index 0000000..a158b57 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SetValue.java @@ -0,0 +1,36 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.ArgumentHolder; +import com.j256.ormlite.stmt.NullArgHolder; +import com.j256.ormlite.stmt.StatementBuilder; + +/** + * Internal class handling the SQL SET part used by UPDATE statements. Used by + * {@link StatementBuilder#updateColumnValue(String, Object)}. + * + *

+ * It's not a comparison per se but does have a columnName = value form so it works. + *

+ * + * @author graywatson + */ +public class SetValue extends BaseComparison { + + /** + * Special value in case we are trying to set a field to null. We can't just use the null value because it looks + * like the argument has not been set in the base class. + */ + private static final ArgumentHolder nullValue = new NullArgHolder(); + + public SetValue(String columnName, FieldType fieldType, Object value) throws SQLException { + super(columnName, fieldType, (value == null ? nullValue : value), false); + } + + @Override + public void appendOperation(StringBuilder sb) { + sb.append("= "); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SimpleComparison.java b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SimpleComparison.java new file mode 100644 index 0000000..2be3fc7 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/stmt/query/SimpleComparison.java @@ -0,0 +1,34 @@ +package com.j256.ormlite.stmt.query; + +import java.sql.SQLException; + +import com.j256.ormlite.field.FieldType; + +/** + * Internal class handling a simple comparison query part where the operation is passed in. + * + * @author graywatson + */ +public class SimpleComparison extends BaseComparison { + + public final static String EQUAL_TO_OPERATION = "="; + public final static String GREATER_THAN_OPERATION = ">"; + public final static String GREATER_THAN_EQUAL_TO_OPERATION = ">="; + public final static String LESS_THAN_OPERATION = "<"; + public final static String LESS_THAN_EQUAL_TO_OPERATION = "<="; + public final static String LIKE_OPERATION = "LIKE"; + public final static String NOT_EQUAL_TO_OPERATION = "<>"; + + private final String operation; + + public SimpleComparison(String columnName, FieldType fieldType, Object value, String operation) throws SQLException { + super(columnName, fieldType, value, true); + this.operation = operation; + } + + @Override + public void appendOperation(StringBuilder sb) { + sb.append(operation); + sb.append(' '); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/BaseConnectionSource.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/BaseConnectionSource.java new file mode 100644 index 0000000..37113c2 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/BaseConnectionSource.java @@ -0,0 +1,140 @@ +package com.j256.ormlite.support; + +import java.sql.SQLException; + +import com.j256.ormlite.logger.Logger; + +/** + * Connection source base class which provides the save/clear mechanism using a thread local. + * + * @author graywatson + */ +public abstract class BaseConnectionSource implements ConnectionSource { + + private ThreadLocal specialConnection = new ThreadLocal(); + + public DatabaseConnection getSpecialConnection() { + NestedConnection currentSaved = specialConnection.get(); + if (currentSaved == null) { + return null; + } else { + return currentSaved.connection; + } + } + + /** + * Returns the connection that has been saved or null if none. + */ + protected DatabaseConnection getSavedConnection() { + NestedConnection nested = specialConnection.get(); + if (nested == null) { + return null; + } else { + return nested.connection; + } + } + + /** + * Return true if the connection being released is the one that has been saved. + */ + protected boolean isSavedConnection(DatabaseConnection connection) { + NestedConnection currentSaved = specialConnection.get(); + if (currentSaved == null) { + return false; + } else if (currentSaved.connection == connection) { + // ignore the release when we have a saved connection + return true; + } else { + return false; + } + } + + /** + * Save this connection as our special connection to be returned by the {@link #getSavedConnection()} method. + * + * @return True if the connection was saved or false if it was already saved. + */ + protected boolean saveSpecial(DatabaseConnection connection) throws SQLException { + // check for a connection already saved + NestedConnection currentSaved = specialConnection.get(); + if (currentSaved == null) { + specialConnection.set(new NestedConnection(connection)); + return true; + } else { + if (currentSaved.connection != connection) { + throw new SQLException("trying to save connection " + connection + + " but already have saved connection " + currentSaved.connection); + } + // we must have a save call within another save + currentSaved.increment(); + return false; + } + } + + /** + * Clear the connection that was previously saved. + * + * @return True if the connection argument had been saved. + */ + protected boolean clearSpecial(DatabaseConnection connection, Logger logger) { + NestedConnection currentSaved = specialConnection.get(); + boolean cleared = false; + if (connection == null) { + // ignored + } else if (currentSaved == null) { + logger.error("no connection has been saved when clear() called"); + } else if (currentSaved.connection == connection) { + if (currentSaved.decrementAndGet() == 0) { + // we only clear the connection if nested counter is 0 + specialConnection.set(null); + } + cleared = true; + } else { + logger.error("connection saved {} is not the one being cleared {}", currentSaved.connection, connection); + } + // release should then be called after clear + return cleared; + } + + /** + * Return true if the two connections seem to one one connection under the covers. + */ + protected boolean isSingleConnection(DatabaseConnection conn1, DatabaseConnection conn2) throws SQLException { + // initialize the connections auto-commit flags + conn1.setAutoCommit(true); + conn2.setAutoCommit(true); + try { + // change conn1's auto-commit to be false + conn1.setAutoCommit(false); + if (conn2.isAutoCommit()) { + // if the 2nd connection's auto-commit is still true then we have multiple connections + return false; + } else { + // if the 2nd connection's auto-commit is also false then we have a single connection + return true; + } + } finally { + // restore its auto-commit + conn1.setAutoCommit(true); + } + } + + private static class NestedConnection { + public final DatabaseConnection connection; + private int nestedC; + + public NestedConnection(DatabaseConnection connection) { + this.connection = connection; + this.nestedC = 1; + } + + public void increment() { + nestedC++; + } + + public int decrementAndGet() { + nestedC--; + return nestedC; + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/CompiledStatement.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/CompiledStatement.java new file mode 100644 index 0000000..69c55d7 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/CompiledStatement.java @@ -0,0 +1,79 @@ +package com.j256.ormlite.support; + +import java.io.Closeable; +import java.sql.SQLException; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.field.SqlType; + +/** + * An internal reduction of the SQL PreparedStatment so we can implement its functionality outside of JDBC. + * + * @author graywatson + */ +public interface CompiledStatement extends Closeable { + + /** + * Returns the number of columns in this statement. + */ + public int getColumnCount() throws SQLException; + + /** + * Get the designated column's name. + */ + public String getColumnName(int columnIndex) throws SQLException; + + /** + * Run the prepared update statement returning the number of rows affected. + */ + public int runUpdate() throws SQLException; + + /** + * Run the prepared query statement returning the results. + */ + public DatabaseResults runQuery(ObjectCache objectCache) throws SQLException; + + /** + * Run the prepared execute statement returning the number of rows affected. + */ + public int runExecute() throws SQLException; + + /** + * Close the statement but swallows any SQLExceptions. + */ + public void closeQuietly(); + + /** + * Cancel a currently running query associated with this statement. Support for this is highly architecture and + * database dependent. + */ + public void cancel() throws SQLException; + + /** + * Set the parameter specified by the index and type to be an object. + * + * @param parameterIndex + * Index of the parameter with 0 being the first parameter, etc.. + * @param obj + * Object that we are setting. Can be null. + * @param sqlType + * SQL type of the parameter. + */ + public void setObject(int parameterIndex, Object obj, SqlType sqlType) throws SQLException; + + /** + * Set the number of rows to return in the results. + */ + public void setMaxRows(int max) throws SQLException; + + /** + * Set the query timeout in milliseconds. This may or may not be supported by all database types. Although this is + * in milliseconds, the underlying timeout resolution may be in seconds. + * + *

+ * WARNING: This will stop the query connection but it will _not_ terminate the query if it is already be + * running by the database. + *

+ */ + public void setQueryTimeout(long millis) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/ConnectionSource.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/ConnectionSource.java new file mode 100644 index 0000000..7901a6e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/ConnectionSource.java @@ -0,0 +1,83 @@ +package com.j256.ormlite.support; + +import java.io.Closeable; +import java.sql.SQLException; + +import com.j256.ormlite.db.DatabaseType; + +/** + * A reduction of the SQL DataSource so we can implement its functionality outside of JDBC. + * + * @author graywatson + */ +public interface ConnectionSource extends Closeable { + + /** + * Return a database connection suitable for read-only operations. After you are done, you should call + * {@link #releaseConnection(DatabaseConnection)}. + */ + public DatabaseConnection getReadOnlyConnection() throws SQLException; + + /** + * Return a database connection suitable for read or write operations. After you are done, you should call + * {@link #releaseConnection(DatabaseConnection)}. + */ + public DatabaseConnection getReadWriteConnection() throws SQLException; + + /** + * Release a database connection previously returned by {@link #getReadOnlyConnection()} or + * {@link #getReadWriteConnection()}. + */ + public void releaseConnection(DatabaseConnection connection) throws SQLException; + + /** + * Save this connection and return it for all calls to {@link #getReadOnlyConnection()} and + * {@link #getReadWriteConnection()} unless the {@link #clearSpecialConnection(DatabaseConnection)} method is + * called, all This is used by the transaction mechanism since since all operations within a transaction must + * operate on the same connection. It is also used by the Android code during initialization. + * + *

+ * NOTE: This should be a read-write connection since transactions and Android need it to be so. + *

+ * + *

+ * NOTE: Saving a connection is usually accomplished using ThreadLocals so multiple threads should not be + * using connections in this scenario. + *

+ * + * @return True if the connection was saved or false if we were already inside of a saved connection. + */ + public boolean saveSpecialConnection(DatabaseConnection connection) throws SQLException; + + /** + * Clear the saved connection. + */ + public void clearSpecialConnection(DatabaseConnection connection); + + /** + * Return the currently saved connection or null if none. + */ + public DatabaseConnection getSpecialConnection(); + + /** + * Close any outstanding database connections. + */ + public void closeQuietly(); + + /** + * Return the DatabaseTypre associated with this connection. + */ + public DatabaseType getDatabaseType(); + + /** + * Return true if the connection source is open. Once {@link #close()} has been called, this should return false. + */ + public boolean isOpen(); + + /** + * Return true if there is only one connection to the database being used by this connection-sourse. If true then + * some synchronization will be enabled when using batch tasks. The user will also need to synchronize around some + * of the transaction and auto-commit calls. + */ + public boolean isSingleConnection(); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnection.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnection.java new file mode 100644 index 0000000..d13d6d7 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnection.java @@ -0,0 +1,181 @@ +package com.j256.ormlite.support; + +import java.io.Closeable; +import java.sql.SQLException; +import java.sql.Savepoint; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; + +/** + * A reduction of the SQL Connection so we can implement its functionality outside of JDBC. + * + * @author graywatson + */ +public interface DatabaseConnection extends Closeable { + + /** returned by {@link #queryForOne} if more than one result was found by the query */ + public final static Object MORE_THAN_ONE = new Object(); + public final static int DEFAULT_RESULT_FLAGS = -1; + + /** + * Return if auto-commit is supported. + */ + public boolean isAutoCommitSupported() throws SQLException; + + /** + * Return if auto-commit is currently enabled. + */ + public boolean isAutoCommit() throws SQLException; + + /** + * Set the auto-commit to be on (true) or off (false). Setting auto-commit to true may or may-not cause a commit + * depending on the underlying database code. + */ + public void setAutoCommit(boolean autoCommit) throws SQLException; + + /** + * Start a save point with a certain name. It can be a noop if savepoints are not supported. + * + * @param savePointName + * to use for the Savepoint although it can be ignored. + * + * @return A SavePoint object with which we can release or commit in the future or null if none. + */ + public Savepoint setSavePoint(String savePointName) throws SQLException; + + /** + * Commit all changes since the savepoint was created. If savePoint is null then commit all outstanding changes. + * + * @param savePoint + * That was returned by setSavePoint or null if none. + */ + public void commit(Savepoint savePoint) throws SQLException; + + /** + * Roll back all changes since the savepoint was created. If savePoint is null then roll back all outstanding + * changes. + * + * @param savePoint + * That was returned by setSavePoint previously or null if none. + */ + public void rollback(Savepoint savePoint) throws SQLException; + + /** + * Execute a statement directly on the connection. + * + * @param resultFlags + * Allows specification of some result flags. This is dependent on the backend and database type. Set to + * {@link #DEFAULT_RESULT_FLAGS} for the internal default. + */ + public int executeStatement(String statementStr, int resultFlags) throws SQLException; + + /** + * Like compileStatement(String, StatementType, FieldType[]) except the caller can specify the result flags. + * + * @param resultFlags + * Allows specification of some result flags. This is dependent on the backend and database type. Set to + * {@link #DEFAULT_RESULT_FLAGS} for the internal default. + */ + public CompiledStatement compileStatement(String statement, StatementType type, FieldType[] argFieldTypes, + int resultFlags) throws SQLException; + + /** + * Perform a SQL update while with the associated SQL statement, arguments, and types. This will possibly return + * generated keys if kyeHolder is not null. + * + * @param statement + * SQL statement to use for inserting. + * @param args + * Object arguments for the SQL '?'s. + * @param argfieldTypes + * Field types of the arguments. + * @param keyHolder + * The holder that gets set with the generated key value which may be null. + * @return The number of rows affected by the update. With some database types, this value may be invalid. + */ + public int insert(String statement, Object[] args, FieldType[] argfieldTypes, GeneratedKeyHolder keyHolder) + throws SQLException; + + /** + * Perform a SQL update with the associated SQL statement, arguments, and types. + * + * @param statement + * SQL statement to use for updating. + * @param args + * Object arguments for the SQL '?'s. + * @param argfieldTypes + * Field types of the arguments. + * @return The number of rows affected by the update. With some database types, this value may be invalid. + */ + public int update(String statement, Object[] args, FieldType[] argfieldTypes) throws SQLException; + + /** + * Perform a SQL delete with the associated SQL statement, arguments, and types. + * + * @param statement + * SQL statement to use for deleting. + * @param args + * Object arguments for the SQL '?'s. + * @param argfieldTypes + * Field types of the arguments. + * @return The number of rows affected by the update. With some database types, this value may be invalid. + */ + public int delete(String statement, Object[] args, FieldType[] argfieldTypes) throws SQLException; + + /** + * Perform a SQL query with the associated SQL statement, arguments, and types and returns a single result. + * + * @param statement + * SQL statement to use for deleting. + * @param args + * Object arguments for the SQL '?'s. + * @param argfieldTypes + * Field types of the arguments. + * @param rowMapper + * The mapper to use to convert the row into the returned object. + * @param objectCache + * Any object cache associated with the query or null if none. + * @return The first data item returned by the query which can be cast to T, null if none, the object + * {@link #MORE_THAN_ONE} if more than one result was found. + */ + public Object queryForOne(String statement, Object[] args, FieldType[] argfieldTypes, + GenericRowMapper rowMapper, ObjectCache objectCache) throws SQLException; + + /** + * Perform a query whose result should be a single long-integer value. + * + * @param statement + * SQL statement to use for the query. + */ + public long queryForLong(String statement) throws SQLException; + + /** + * Perform a query whose result should be a single long-integer value. + * + * @param statement + * SQL statement to use for the query. + * @param args + * Arguments to pass into the query. + * @param argFieldTypes + * Field types that correspond to the args. + */ + public long queryForLong(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException; + + /** + * Close the connection to the database but swallow any exceptions. + */ + public void closeQuietly(); + + /** + * Return if the connection has been closed either through a call to {@link #close()} or because of a fatal error. + */ + public boolean isClosed() throws SQLException; + + /** + * Return true if the table exists in the database. + */ + public boolean isTableExists(String tableName) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxy.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxy.java new file mode 100644 index 0000000..d381d15 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxy.java @@ -0,0 +1,167 @@ +package com.j256.ormlite.support; + +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Savepoint; + +import com.j256.ormlite.dao.ObjectCache; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.stmt.GenericRowMapper; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; + +/** + * Database connection proxy so you can intercept database operations either for debugging, replication, logging, or + * other purposes. This is designed to be extended by a subclass with particular methods overridden by the subclass to + * do monitoring, logging, or to do some special hackery. + * + *

+ * See the {@link DatabaseConnectionProxyFactory} javadocs for more details. + *

+ * + * @author graywatson + */ +public class DatabaseConnectionProxy implements DatabaseConnection { + + private final DatabaseConnection proxy; + + public DatabaseConnectionProxy(DatabaseConnection proxy) { + this.proxy = proxy; + } + + public boolean isAutoCommitSupported() throws SQLException { + if (proxy == null) { + return false; + } else { + return proxy.isAutoCommitSupported(); + } + } + + public boolean isAutoCommit() throws SQLException { + if (proxy == null) { + return false; + } else { + return proxy.isAutoCommit(); + } + } + + public void setAutoCommit(boolean autoCommit) throws SQLException { + if (proxy != null) { + proxy.setAutoCommit(autoCommit); + } + } + + public Savepoint setSavePoint(String name) throws SQLException { + if (proxy == null) { + return null; + } else { + return proxy.setSavePoint(name); + } + } + + public void commit(Savepoint savePoint) throws SQLException { + if (proxy != null) { + proxy.commit(savePoint); + } + } + + public void rollback(Savepoint savePoint) throws SQLException { + if (proxy != null) { + proxy.rollback(savePoint); + } + } + + public int executeStatement(String statementStr, int resultFlags) throws SQLException { + if (proxy == null) { + return 0; + } else { + return proxy.executeStatement(statementStr, resultFlags); + } + } + + public CompiledStatement compileStatement(String statement, StatementType type, FieldType[] argFieldTypes, + int resultFlags) throws SQLException { + if (proxy == null) { + return null; + } else { + return proxy.compileStatement(statement, type, argFieldTypes, resultFlags); + } + } + + public int insert(String statement, Object[] args, FieldType[] argfieldTypes, GeneratedKeyHolder keyHolder) + throws SQLException { + if (proxy == null) { + return 0; + } else { + return proxy.insert(statement, args, argfieldTypes, keyHolder); + } + } + + public int update(String statement, Object[] args, FieldType[] argfieldTypes) throws SQLException { + if (proxy == null) { + return 0; + } else { + return proxy.update(statement, args, argfieldTypes); + } + } + + public int delete(String statement, Object[] args, FieldType[] argfieldTypes) throws SQLException { + if (proxy == null) { + return 0; + } else { + return proxy.delete(statement, args, argfieldTypes); + } + } + + public Object queryForOne(String statement, Object[] args, FieldType[] argfieldTypes, + GenericRowMapper rowMapper, ObjectCache objectCache) throws SQLException { + if (proxy == null) { + return null; + } else { + return proxy.queryForOne(statement, args, argfieldTypes, rowMapper, objectCache); + } + } + + public long queryForLong(String statement) throws SQLException { + if (proxy == null) { + return 0; + } else { + return proxy.queryForLong(statement); + } + } + + public long queryForLong(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { + if (proxy == null) { + return 0; + } else { + return proxy.queryForLong(statement, args, argFieldTypes); + } + } + + public void close() throws IOException { + if (proxy != null) { + proxy.close(); + } + } + + public void closeQuietly() { + if (proxy != null) { + proxy.closeQuietly(); + } + } + + public boolean isClosed() throws SQLException { + if (proxy == null) { + return true; + } else { + return proxy.isClosed(); + } + } + + public boolean isTableExists(String tableName) throws SQLException { + if (proxy == null) { + return false; + } else { + return proxy.isTableExists(tableName); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxyFactory.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxyFactory.java new file mode 100644 index 0000000..01f11a6 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseConnectionProxyFactory.java @@ -0,0 +1,58 @@ +package com.j256.ormlite.support; + +import java.sql.SQLException; + +/** + * Defines a class that creates connection proxies. This can be set on the {@code JdbcConnectionSource} or + * {@code AndroidConnectionSource} using the {@code setDatabaseConnectionProxyFactory(...)} static method on each class. + * + *

+ * Typically you create a subclass of {@link DatabaseConnectionProxy} and override the methods that you want to wrap for + * logging, monitoring, or other reason. Something like: + *

+ * + *
+ * private static class MyConnectionProxy extends DatabaseConnectionProxy {
+ * 	public ConnectionProxy(DatabaseConnection conn) {
+ * 		super(conn);
+ * 	}
+ * 	@Override
+ * 	public int insert(String statement, Object[] args, FieldType[] argfieldTypes, GeneratedKeyHolder keyHolder)
+ * 			throws SQLException {
+ * 		// do something here to the arguments or write to a log or something
+ * 		return super.insert(statement, args, argfieldTypes, keyHolder);
+ * 	}
+ * }
+ * 
+ * + *

+ * Then define your own factory which constructs instances of your proxy object. For example: + *

+ * + *
+ * JdbcConnectionSource.setDatabaseConnectionProxyFactory(new DatabaseConnectionProxyFactory() {
+ * 	public DatabaseConnection createProxy(DatabaseConnection realConnection) {
+ * 		return new MyConnectionProxy(realConnection);
+ * 	}
+ * });
+ * 
+ * + *

+ * You can also use the {@link ReflectionDatabaseConnectionProxyFactory} which takes a class and constructs your proxy + * subclass using reflection. + *

+ * + *

+ * To see a working example of the connection proxy, see the DatabaseConnectionProxyFactoryTest. + *

+ * + * @author graywatson + */ +public interface DatabaseConnectionProxyFactory { + + /** + * Create a proxy database connection that may extend {@link DatabaseConnectionProxy}. This method should + * instantiate the proxy and set the real-connection on it. + */ + public DatabaseConnection createProxy(DatabaseConnection realConnection) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseResults.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseResults.java new file mode 100644 index 0000000..b91a01e --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/DatabaseResults.java @@ -0,0 +1,168 @@ +package com.j256.ormlite.support; + +import java.io.Closeable; +import java.io.InputStream; +import java.math.BigDecimal; +import java.sql.SQLException; +import java.sql.Timestamp; + +import com.j256.ormlite.dao.ObjectCache; + +/** + * A reduction of the SQL ResultSet so we can implement it outside of JDBC. + * + *

+ * NOTE: In all cases, the columnIndex parameters are 0 based -- not 1 based like JDBC. + *

+ * + * @author graywatson + */ +public interface DatabaseResults extends Closeable { + + /** + * Returns the number of columns in these results. + */ + public int getColumnCount() throws SQLException; + + /** + * Returns an array of column names. + */ + public String[] getColumnNames() throws SQLException; + + /** + * Moves to the first result. This may not work with the default iterator depending on your database. + * + * @return true if there are more results to be processed. + */ + public boolean first() throws SQLException; + + /** + * Moves to the previous result. This may not work with the default iterator depending on your database. + * + * @return true if there are more results to be processed. + */ + public boolean previous() throws SQLException; + + /** + * Moves to the next result. + * + * @return true if there are more results to be processed. + */ + public boolean next() throws SQLException; + + /** + * Moves to the last result. This may not work with the default iterator depending on your database. + * + * @return true if there are more results to be processed. + */ + public boolean last() throws SQLException; + + /** + * Moves forward (positive value) or backwards (negative value) the list of results. moveRelative(1) should be the + * same as {@link #next()}. moveRelative(-1) is the same as {@link #previous} result. This may not work with the + * default iterator depending on your database. + * + * @param offset + * Number of rows to move. Positive moves forward in the results. Negative moves backwards. + * @return true if there are more results to be processed. + */ + public boolean moveRelative(int offset) throws SQLException; + + /** + * Moves to an absolute position in the list of results. This may not work with the default iterator depending on + * your database. + * + * @param position + * Row number in the result list to move to. + * @return true if there are more results to be processed. + */ + public boolean moveAbsolute(int position) throws SQLException; + + /** + * Returns the column index associated with the column name. + * + * @throws SQLException + * if the column was not found in the results. + */ + public int findColumn(String columnName) throws SQLException; + + /** + * Returns the string from the results at the column index. + */ + public String getString(int columnIndex) throws SQLException; + + /** + * Returns the boolean value from the results at the column index. + */ + public boolean getBoolean(int columnIndex) throws SQLException; + + /** + * Returns the char value from the results at the column index. + */ + public char getChar(int columnIndex) throws SQLException; + + /** + * Returns the byte value from the results at the column index. + */ + public byte getByte(int columnIndex) throws SQLException; + + /** + * Returns the byte array value from the results at the column index. + */ + public byte[] getBytes(int columnIndex) throws SQLException; + + /** + * Returns the short value from the results at the column index. + */ + public short getShort(int columnIndex) throws SQLException; + + /** + * Returns the integer value from the results at the column index. + */ + public int getInt(int columnIndex) throws SQLException; + + /** + * Returns the long value from the results at the column index. + */ + public long getLong(int columnIndex) throws SQLException; + + /** + * Returns the float value from the results at the column index. + */ + public float getFloat(int columnIndex) throws SQLException; + + /** + * Returns the double value from the results at the column index. + */ + public double getDouble(int columnIndex) throws SQLException; + + /** + * Returns the SQL timestamp value from the results at the column index. + */ + public Timestamp getTimestamp(int columnIndex) throws SQLException; + + /** + * Returns an input stream for a blob value from the results at the column index. + */ + public InputStream getBlobStream(int columnIndex) throws SQLException; + + /** + * Returns the SQL big decimal value from the results at the column index. + */ + public BigDecimal getBigDecimal(int columnIndex) throws SQLException; + + /** + * Returns true if the last object returned with the column index is null. + */ + public boolean wasNull(int columnIndex) throws SQLException; + + /** + * Returns the object cache that is associated with these results or null if none. + */ + public ObjectCache getObjectCache(); + + /** + * Closes any underlying database connections but swallows any SQLExceptions. + */ + public void closeQuietly(); +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/GeneratedKeyHolder.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/GeneratedKeyHolder.java new file mode 100644 index 0000000..a0a8e9c --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/GeneratedKeyHolder.java @@ -0,0 +1,16 @@ +package com.j256.ormlite.support; + +import java.sql.SQLException; + +/** + * The holder of a generated key so we can return the value of generated keys from update methods. + * + * @author graywatson + */ +public interface GeneratedKeyHolder { + + /** + * Add the key number on the key holder. May be called multiple times. + */ + public void addKey(Number key) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/support/ReflectionDatabaseConnectionProxyFactory.java b/MemeProject/app/src/main/java/com/j256/ormlite/support/ReflectionDatabaseConnectionProxyFactory.java new file mode 100644 index 0000000..1c03225 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/support/ReflectionDatabaseConnectionProxyFactory.java @@ -0,0 +1,43 @@ +package com.j256.ormlite.support; + +import java.lang.reflect.Constructor; +import java.sql.SQLException; + +import com.j256.ormlite.misc.SqlExceptionUtil; + +/** + * Database connection proxy factory that uses reflection to create the proxied connection. The class in question should + * have a constructor that takes a {@link DatabaseConnection} object. + * + * @author graywatson + */ +public class ReflectionDatabaseConnectionProxyFactory implements DatabaseConnectionProxyFactory { + + private final Class proxyClass; + private final Constructor constructor; + + /** + * Takes a proxy-class that will be used to instantiate an instance in {@link #createProxy(DatabaseConnection)}. + * + * @throws IllegalArgumentException + * if the class does not have a constructor that takes a {@link DatabaseConnection} object. + */ + public ReflectionDatabaseConnectionProxyFactory(Class proxyClass) + throws IllegalArgumentException { + this.proxyClass = proxyClass; + try { + this.constructor = proxyClass.getConstructor(DatabaseConnection.class); + } catch (Exception e) { + throw new IllegalArgumentException("Could not find constructor with DatabaseConnection argument in " + + proxyClass); + } + } + + public DatabaseConnection createProxy(DatabaseConnection realConnection) throws SQLException { + try { + return constructor.newInstance(realConnection); + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not create a new instance of " + proxyClass, e); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTable.java b/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTable.java new file mode 100644 index 0000000..b6e83db --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTable.java @@ -0,0 +1,43 @@ +package com.j256.ormlite.table; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.j256.ormlite.dao.DaoManager; + +/** + * Annotation that marks a class to be stored in the database. It is only required if you want to mark the class or + * change its default tableName. You specify this annotation above the classes that you want to persist to the database. + * For example: + * + *
+ * @DatabaseTable(tableName = "accounts")
+ * public class Account {
+ *   ...
+ * 
+ * + *

+ * NOTE: Classes that are persisted using this package must have a no-argument constructor with at least + * package visibility so objects can be created when you do a query, etc.. + *

+ * + * @author graywatson + */ +@Target(TYPE) +@Retention(RUNTIME) +public @interface DatabaseTable { + + /** + * The name of the column in the database. If not set then the name is taken from the class name lowercased. + */ + String tableName() default ""; + + /** + * The DAO class that corresponds to this class. This is used by the {@link DaoManager} when it constructs a DAO + * internally. + */ + Class daoClass() default Void.class; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfig.java b/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfig.java new file mode 100644 index 0000000..e7950ed --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfig.java @@ -0,0 +1,265 @@ +package com.j256.ormlite.table; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.DatabaseFieldConfig; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.JavaxPersistenceConfigurer; +import com.j256.ormlite.support.ConnectionSource; + +/** + * Database table configuration information either supplied by Spring or direct Java wiring or from a + * {@link DatabaseTable} annotation. + * + * @author graywatson + */ +public class DatabaseTableConfig { + + private static JavaxPersistenceConfigurer javaxPersistenceConfigurer; + + private Class dataClass; + private String tableName; + private List fieldConfigs; + private FieldType[] fieldTypes; + private Constructor constructor; + + static { + try { + // see if we have this class at runtime + Class.forName("javax.persistence.Entity"); + // if we do then get our JavaxPersistance class + Class clazz = Class.forName("com.j256.ormlite.misc.JavaxPersistenceImpl"); + javaxPersistenceConfigurer = (JavaxPersistenceConfigurer) clazz.getConstructor().newInstance(); + } catch (Exception e) { + // no configurer + javaxPersistenceConfigurer = null; + } + } + + public DatabaseTableConfig() { + // for spring + } + + /** + * Setup a table config associated with the dataClass and field configurations. The table-name will be extracted + * from the dataClass. + */ + public DatabaseTableConfig(Class dataClass, List fieldConfigs) { + this(dataClass, extractTableName(dataClass), fieldConfigs); + } + + /** + * Setup a table config associated with the dataClass, table-name, and field configurations. + */ + public DatabaseTableConfig(Class dataClass, String tableName, List fieldConfigs) { + this.dataClass = dataClass; + this.tableName = tableName; + this.fieldConfigs = fieldConfigs; + } + + private DatabaseTableConfig(Class dataClass, String tableName, FieldType[] fieldTypes) { + this.dataClass = dataClass; + this.tableName = tableName; + this.fieldTypes = fieldTypes; + } + + /** + * Initialize the class if this is being called with Spring. + */ + public void initialize() { + if (dataClass == null) { + throw new IllegalStateException("dataClass was never set on " + getClass().getSimpleName()); + } + if (tableName == null) { + tableName = extractTableName(dataClass); + } + } + + public Class getDataClass() { + return dataClass; + } + + // @Required + public void setDataClass(Class dataClass) { + this.dataClass = dataClass; + } + + public String getTableName() { + return tableName; + } + + /** + * Set the table name. If not specified then the name is gotten from the class name. + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public void setFieldConfigs(List fieldConfigs) { + this.fieldConfigs = fieldConfigs; + } + + /** + * Extract the field types from the fieldConfigs if they have not already been configured. + */ + public void extractFieldTypes(ConnectionSource connectionSource) throws SQLException { + if (fieldTypes == null) { + if (fieldConfigs == null) { + fieldTypes = extractFieldTypes(connectionSource, dataClass, tableName); + } else { + fieldTypes = convertFieldConfigs(connectionSource, tableName, fieldConfigs); + } + } + } + + /** + * Return the field types associated with this configuration. + */ + public FieldType[] getFieldTypes(DatabaseType databaseType) throws SQLException { + if (fieldTypes == null) { + throw new SQLException("Field types have not been extracted in table config"); + } + return fieldTypes; + } + + public List getFieldConfigs() { + return fieldConfigs; + } + + /** + * Return the constructor for this class. If not constructor has been set on the class then it will be found on the + * class through reflection. + */ + public Constructor getConstructor() { + if (constructor == null) { + constructor = findNoArgConstructor(dataClass); + } + return constructor; + } + + // @NotRequired + public void setConstructor(Constructor constructor) { + this.constructor = constructor; + } + + /** + * Extract the DatabaseTableConfig for a particular class by looking for class and field annotations. This is used + * by internal classes to configure a class. + */ + public static DatabaseTableConfig fromClass(ConnectionSource connectionSource, Class clazz) + throws SQLException { + String tableName = extractTableName(clazz); + if (connectionSource.getDatabaseType().isEntityNamesMustBeUpCase()) { + tableName = tableName.toUpperCase(); + } + return new DatabaseTableConfig(clazz, tableName, extractFieldTypes(connectionSource, clazz, tableName)); + } + + /** + * Extract and return the table name for a class. + */ + public static String extractTableName(Class clazz) { + DatabaseTable databaseTable = clazz.getAnnotation(DatabaseTable.class); + String name = null; + if (databaseTable != null && databaseTable.tableName() != null && databaseTable.tableName().length() > 0) { + name = databaseTable.tableName(); + } + if (name == null && javaxPersistenceConfigurer != null) { + name = javaxPersistenceConfigurer.getEntityName(clazz); + } + if (name == null) { + // if the name isn't specified, it is the class name lowercased + name = clazz.getSimpleName().toLowerCase(); + } + return name; + } + + /** + * Locate the no arg constructor for the class. + */ + public static Constructor findNoArgConstructor(Class dataClass) { + Constructor[] constructors; + try { + @SuppressWarnings("unchecked") + Constructor[] consts = (Constructor[]) dataClass.getDeclaredConstructors(); + // i do this [grossness] to be able to move the Suppress inside the method + constructors = consts; + } catch (Exception e) { + throw new IllegalArgumentException("Can't lookup declared constructors for " + dataClass, e); + } + for (Constructor con : constructors) { + if (con.getParameterTypes().length == 0) { + if (!con.isAccessible()) { + try { + con.setAccessible(true); + } catch (SecurityException e) { + throw new IllegalArgumentException("Could not open access to constructor for " + dataClass); + } + } + return con; + } + } + if (dataClass.getEnclosingClass() == null) { + throw new IllegalArgumentException("Can't find a no-arg constructor for " + dataClass); + } else { + throw new IllegalArgumentException("Can't find a no-arg constructor for " + dataClass + + ". Missing static on inner class?"); + } + } + + private static FieldType[] extractFieldTypes(ConnectionSource connectionSource, Class clazz, String tableName) + throws SQLException { + List fieldTypes = new ArrayList(); + for (Class classWalk = clazz; classWalk != null; classWalk = classWalk.getSuperclass()) { + for (Field field : classWalk.getDeclaredFields()) { + FieldType fieldType = FieldType.createFieldType(connectionSource, tableName, field, clazz); + if (fieldType != null) { + fieldTypes.add(fieldType); + } + } + } + if (fieldTypes.isEmpty()) { + throw new IllegalArgumentException("No fields have a " + DatabaseField.class.getSimpleName() + + " annotation in " + clazz); + } + return fieldTypes.toArray(new FieldType[fieldTypes.size()]); + } + + private FieldType[] convertFieldConfigs(ConnectionSource connectionSource, String tableName, + List fieldConfigs) throws SQLException { + List fieldTypes = new ArrayList(); + for (DatabaseFieldConfig fieldConfig : fieldConfigs) { + FieldType fieldType = null; + // walk up the classes until we find the field + for (Class classWalk = dataClass; classWalk != null; classWalk = classWalk.getSuperclass()) { + Field field; + try { + field = classWalk.getDeclaredField(fieldConfig.getFieldName()); + } catch (NoSuchFieldException e) { + // we ignore this and just loop hopefully finding it in a upper class + continue; + } + if (field != null) { + fieldType = new FieldType(connectionSource, tableName, field, fieldConfig, dataClass); + break; + } + } + + if (fieldType == null) { + throw new SQLException("Could not find declared field with name '" + fieldConfig.getFieldName() + + "' for " + dataClass); + } + fieldTypes.add(fieldType); + } + if (fieldTypes.isEmpty()) { + throw new SQLException("No fields were configured for class " + dataClass); + } + return fieldTypes.toArray(new FieldType[fieldTypes.size()]); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfigLoader.java b/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfigLoader.java new file mode 100644 index 0000000..9b38181 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/table/DatabaseTableConfigLoader.java @@ -0,0 +1,171 @@ +package com.j256.ormlite.table; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.j256.ormlite.field.DatabaseFieldConfig; +import com.j256.ormlite.field.DatabaseFieldConfigLoader; +import com.j256.ormlite.misc.SqlExceptionUtil; + +/** + * Database table configuration loader which reads and writes {@link DatabaseTableConfig} from a text file/stream. + * + * @author graywatson + */ +public class DatabaseTableConfigLoader { + + private static final String CONFIG_FILE_START_MARKER = "# --table-start--"; + private static final String CONFIG_FILE_END_MARKER = "# --table-end--"; + private static final String CONFIG_FILE_FIELDS_START = "# --table-fields-start--"; + private static final String CONFIG_FILE_FIELDS_END = "# --table-fields-end--"; + + /** + * Load in a number of database configuration entries from a buffered reader. + */ + public static List> loadDatabaseConfigFromReader(BufferedReader reader) throws SQLException { + List> list = new ArrayList>(); + while (true) { + DatabaseTableConfig config = DatabaseTableConfigLoader.fromReader(reader); + if (config == null) { + break; + } + list.add(config); + } + return list; + } + + /** + * Load a table configuration in from a text-file reader. + * + * @return A config if any of the fields were set otherwise null if we reach EOF. + */ + public static DatabaseTableConfig fromReader(BufferedReader reader) throws SQLException { + DatabaseTableConfig config = new DatabaseTableConfig(); + boolean anything = false; + while (true) { + String line; + try { + line = reader.readLine(); + } catch (IOException e) { + throw SqlExceptionUtil.create("Could not read DatabaseTableConfig from stream", e); + } + if (line == null) { + break; + } + // we do this so we can support multiple class configs per file + if (line.equals(CONFIG_FILE_END_MARKER)) { + break; + } + // we do this so we can support multiple class configs per file + if (line.equals(CONFIG_FILE_FIELDS_START)) { + readFields(reader, config); + continue; + } + // skip empty lines or comments + if (line.length() == 0 || line.startsWith("#") || line.equals(CONFIG_FILE_START_MARKER)) { + continue; + } + String[] parts = line.split("=", -2); + if (parts.length != 2) { + throw new SQLException("DatabaseTableConfig reading from stream cannot parse line: " + line); + } + readTableField(config, parts[0], parts[1]); + anything = true; + } + // if we got any config lines then we return the config + if (anything) { + return config; + } else { + // otherwise we return null for none + return null; + } + } + + /** + * Write the table configuration to a buffered writer. + */ + public static void write(BufferedWriter writer, DatabaseTableConfig config) throws SQLException { + try { + writeConfig(writer, config); + } catch (IOException e) { + throw SqlExceptionUtil.create("Could not write config to writer", e); + } + } + + // field names in the config file + private static final String FIELD_NAME_DATA_CLASS = "dataClass"; + private static final String FIELD_NAME_TABLE_NAME = "tableName"; + + /** + * Write the config to the writer. + */ + private static void writeConfig(BufferedWriter writer, DatabaseTableConfig config) throws IOException, + SQLException { + writer.append(CONFIG_FILE_START_MARKER); + writer.newLine(); + if (config.getDataClass() != null) { + writer.append(FIELD_NAME_DATA_CLASS).append('=').append(config.getDataClass().getName()); + writer.newLine(); + } + if (config.getTableName() != null) { + writer.append(FIELD_NAME_TABLE_NAME).append('=').append(config.getTableName()); + writer.newLine(); + } + writer.append(CONFIG_FILE_FIELDS_START); + writer.newLine(); + if (config.getFieldConfigs() != null) { + for (DatabaseFieldConfig field : config.getFieldConfigs()) { + DatabaseFieldConfigLoader.write(writer, field, config.getTableName()); + } + } + writer.append(CONFIG_FILE_FIELDS_END); + writer.newLine(); + writer.append(CONFIG_FILE_END_MARKER); + writer.newLine(); + } + + /** + * Read a field into our table configuration for field=value line. + */ + private static void readTableField(DatabaseTableConfig config, String field, String value) { + if (field.equals(FIELD_NAME_DATA_CLASS)) { + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(value); + config.setDataClass(clazz); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Unknown class specified for dataClass: " + value); + } + } else if (field.equals(FIELD_NAME_TABLE_NAME)) { + config.setTableName(value); + } + } + + /** + * Read all of the fields information from the configuration file. + */ + private static void readFields(BufferedReader reader, DatabaseTableConfig config) throws SQLException { + List fields = new ArrayList(); + while (true) { + String line; + try { + line = reader.readLine(); + } catch (IOException e) { + throw SqlExceptionUtil.create("Could not read next field from config file", e); + } + if (line == null || line.equals(CONFIG_FILE_FIELDS_END)) { + break; + } + DatabaseFieldConfig fieldConfig = DatabaseFieldConfigLoader.fromReader(reader); + if (fieldConfig == null) { + break; + } + fields.add(fieldConfig); + } + config.setFieldConfigs(fields); + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/table/ObjectFactory.java b/MemeProject/app/src/main/java/com/j256/ormlite/table/ObjectFactory.java new file mode 100644 index 0000000..9c142c3 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/table/ObjectFactory.java @@ -0,0 +1,23 @@ +package com.j256.ormlite.table; + +import java.lang.reflect.Constructor; +import java.sql.SQLException; + +import com.j256.ormlite.dao.Dao; + +/** + * Interface that allows you to inject a factory class that creates objects of this class. You sert it on the Dao using: + * {@link Dao#setObjectFactory(ObjectFactory)}. + * + * @author graywatson + */ +public interface ObjectFactory { + + /** + * Construct and return an object of a certain class. + * + * @throws SQLException + * if there was a problem creating the object. + */ + public T createObject(Constructor construcor, Class dataClass) throws SQLException; +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/table/TableInfo.java b/MemeProject/app/src/main/java/com/j256/ormlite/table/TableInfo.java new file mode 100644 index 0000000..3123290 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/table/TableInfo.java @@ -0,0 +1,250 @@ +package com.j256.ormlite.table; + +import java.lang.reflect.Constructor; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import com.j256.ormlite.dao.BaseDaoImpl; +import com.j256.ormlite.dao.ForeignCollection; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.misc.BaseDaoEnabled; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.support.ConnectionSource; + +/** + * Information about a database table including the associated tableName, class, constructor, and the included fields. + * + * @param + * The class that the code will be operating on. + * @param + * The class of the ID column associated with the class. The T class does not require an ID field. The class + * needs an ID parameter however so you can use Void or Object to satisfy the compiler. + * @author graywatson + */ +public class TableInfo { + + private static final FieldType[] NO_FOREIGN_COLLECTIONS = new FieldType[0]; + + private final BaseDaoImpl baseDaoImpl; + private final Class dataClass; + private final String tableName; + private final FieldType[] fieldTypes; + private final FieldType[] foreignCollections; + private final FieldType idField; + private final Constructor constructor; + private final boolean foreignAutoCreate; + private Map fieldNameMap; + + /** + * Creates a holder of information about a table/class. + * + * @param connectionSource + * Source of our database connections. + * @param baseDaoImpl + * Associated BaseDaoImpl. + * @param dataClass + * Class that we are holding information about. + */ + public TableInfo(ConnectionSource connectionSource, BaseDaoImpl baseDaoImpl, Class dataClass) + throws SQLException { + this(connectionSource.getDatabaseType(), baseDaoImpl, + DatabaseTableConfig.fromClass(connectionSource, dataClass)); + } + + /** + * Creates a holder of information about a table/class. + * + * @param databaseType + * Database type we are storing the class in. + * @param baseDaoImpl + * Associated BaseDaoImpl. + * @param tableConfig + * Configuration for our table. + */ + public TableInfo(DatabaseType databaseType, BaseDaoImpl baseDaoImpl, DatabaseTableConfig tableConfig) + throws SQLException { + this.baseDaoImpl = baseDaoImpl; + this.dataClass = tableConfig.getDataClass(); + this.tableName = tableConfig.getTableName(); + this.fieldTypes = tableConfig.getFieldTypes(databaseType); + // find the id field + FieldType findIdFieldType = null; + boolean foreignAutoCreate = false; + int foreignCollectionCount = 0; + for (FieldType fieldType : fieldTypes) { + if (fieldType.isId() || fieldType.isGeneratedId() || fieldType.isGeneratedIdSequence()) { + if (findIdFieldType != null) { + throw new SQLException("More than 1 idField configured for class " + dataClass + " (" + + findIdFieldType + "," + fieldType + ")"); + } + findIdFieldType = fieldType; + } + if (fieldType.isForeignAutoCreate()) { + foreignAutoCreate = true; + } + if (fieldType.isForeignCollection()) { + foreignCollectionCount++; + } + } + // can be null if there is no id field + this.idField = findIdFieldType; + this.constructor = tableConfig.getConstructor(); + this.foreignAutoCreate = foreignAutoCreate; + if (foreignCollectionCount == 0) { + this.foreignCollections = NO_FOREIGN_COLLECTIONS; + } else { + this.foreignCollections = new FieldType[foreignCollectionCount]; + foreignCollectionCount = 0; + for (FieldType fieldType : fieldTypes) { + if (fieldType.isForeignCollection()) { + this.foreignCollections[foreignCollectionCount] = fieldType; + foreignCollectionCount++; + } + } + } + } + + /** + * Return the class associated with this object-info. + */ + public Class getDataClass() { + return dataClass; + } + + /** + * Return the name of the table associated with the object. + */ + public String getTableName() { + return tableName; + } + + /** + * Return the array of field types associated with the object. + */ + public FieldType[] getFieldTypes() { + return fieldTypes; + } + + /** + * Return the {@link FieldType} associated with the columnName. + */ + public FieldType getFieldTypeByColumnName(String columnName) { + if (fieldNameMap == null) { + // build our alias map if we need it + Map map = new HashMap(); + for (FieldType fieldType : fieldTypes) { + map.put(fieldType.getColumnName().toLowerCase(), fieldType); + } + fieldNameMap = map; + } + FieldType fieldType = fieldNameMap.get(columnName.toLowerCase()); + // if column name is found, return it + if (fieldType != null) { + return fieldType; + } + // look to see if someone is using the field-name instead of column-name + for (FieldType fieldType2 : fieldTypes) { + if (fieldType2.getFieldName().equals(columnName)) { + throw new IllegalArgumentException("You should use columnName '" + fieldType2.getColumnName() + + "' for table " + tableName + " instead of fieldName '" + fieldType2.getFieldName() + "'"); + } + } + throw new IllegalArgumentException("Unknown column name '" + columnName + "' in table " + tableName); + } + + /** + * Return the id-field associated with the object. + */ + public FieldType getIdField() { + return idField; + } + + public Constructor getConstructor() { + return constructor; + } + + /** + * Return a string representation of the object. + */ + public String objectToString(T object) { + StringBuilder sb = new StringBuilder(64); + sb.append(object.getClass().getSimpleName()); + for (FieldType fieldType : fieldTypes) { + sb.append(' ').append(fieldType.getColumnName()).append("="); + try { + sb.append(fieldType.extractJavaFieldValue(object)); + } catch (Exception e) { + throw new IllegalStateException("Could not generate toString of field " + fieldType, e); + } + } + return sb.toString(); + } + + /** + * Create and return an object of this type using our reflection constructor. + */ + public T createObject() throws SQLException { + try { + T instance; + ObjectFactory factory = null; + if (baseDaoImpl != null) { + factory = baseDaoImpl.getObjectFactory(); + } + if (factory == null) { + instance = constructor.newInstance(); + } else { + instance = factory.createObject(constructor, baseDaoImpl.getDataClass()); + } + wireNewInstance(baseDaoImpl, instance); + return instance; + } catch (Exception e) { + throw SqlExceptionUtil.create("Could not create object for " + constructor.getDeclaringClass(), e); + } + } + + /** + * Return true if we can update this object via its ID. + */ + public boolean isUpdatable() { + // to update we must have an id field and there must be more than just the id field + return (idField != null && fieldTypes.length > 1); + } + + /** + * Return true if one of the fields has {@link DatabaseField#foreignAutoCreate()} enabled. + */ + public boolean isForeignAutoCreate() { + return foreignAutoCreate; + } + + /** + * Return an array with the fields that are {@link ForeignCollection}s or a blank array if none. + */ + public FieldType[] getForeignCollections() { + return foreignCollections; + } + + /** + * Return true if this table information has a field with this columnName as set by + * {@link DatabaseField#columnName()} or the field name if not set. + */ + public boolean hasColumnName(String columnName) { + for (FieldType fieldType : fieldTypes) { + if (fieldType.getColumnName().equals(columnName)) { + return true; + } + } + return false; + } + + private static void wireNewInstance(BaseDaoImpl baseDaoImpl, T instance) { + if (instance instanceof BaseDaoEnabled) { + @SuppressWarnings("unchecked") + BaseDaoEnabled daoEnabled = (BaseDaoEnabled) instance; + daoEnabled.setDao(baseDaoImpl); + } + } +} diff --git a/MemeProject/app/src/main/java/com/j256/ormlite/table/TableUtils.java b/MemeProject/app/src/main/java/com/j256/ormlite/table/TableUtils.java new file mode 100644 index 0000000..dbcad78 --- /dev/null +++ b/MemeProject/app/src/main/java/com/j256/ormlite/table/TableUtils.java @@ -0,0 +1,523 @@ +package com.j256.ormlite.table; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.j256.ormlite.dao.BaseDaoImpl; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.db.DatabaseType; +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.field.FieldType; +import com.j256.ormlite.logger.Logger; +import com.j256.ormlite.logger.LoggerFactory; +import com.j256.ormlite.misc.IOUtils; +import com.j256.ormlite.misc.SqlExceptionUtil; +import com.j256.ormlite.stmt.StatementBuilder.StatementType; +import com.j256.ormlite.support.CompiledStatement; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.support.DatabaseResults; + +/** + * Couple utility methods for the creating, dropping, and maintenance of tables. + * + * @author graywatson + */ +public class TableUtils { + + private static Logger logger = LoggerFactory.getLogger(TableUtils.class); + private static final FieldType[] noFieldTypes = new FieldType[0]; + + /** + * For static methods only. + */ + private TableUtils() { + } + + /** + * Issue the database statements to create the table associated with a class. + * + * @param connectionSource + * Associated connection source. + * @param dataClass + * The class for which a table will be created. + * @return The number of statements executed to do so. + */ + public static int createTable(ConnectionSource connectionSource, Class dataClass) throws SQLException { + return createTable(connectionSource, dataClass, false); + } + + /** + * Create a table if it does not already exist. This is not supported by all databases. + */ + public static int createTableIfNotExists(ConnectionSource connectionSource, Class dataClass) + throws SQLException { + return createTable(connectionSource, dataClass, true); + } + + /** + * Issue the database statements to create the table associated with a table configuration. + * + * @param connectionSource + * connectionSource Associated connection source. + * @param tableConfig + * Hand or spring wired table configuration. If null then the class must have {@link DatabaseField} + * annotations. + * @return The number of statements executed to do so. + */ + public static int createTable(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) + throws SQLException { + return createTable(connectionSource, tableConfig, false); + } + + /** + * Create a table if it does not already exist. This is not supported by all databases. + */ + public static int createTableIfNotExists(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) + throws SQLException { + return createTable(connectionSource, tableConfig, true); + } + + /** + * Return an ordered collection of SQL statements that need to be run to create a table. To do the work of creating, + * you should call {@link #createTable}. + * + * @param connectionSource + * Our connect source which is used to get the database type, not to apply the creates. + * @param dataClass + * The class for which a table will be created. + * @return The collection of table create statements. + */ + public static List getCreateTableStatements(ConnectionSource connectionSource, Class dataClass) + throws SQLException { + Dao dao = DaoManager.createDao(connectionSource, dataClass); + if (dao instanceof BaseDaoImpl) { + return addCreateTableStatements(connectionSource, ((BaseDaoImpl) dao).getTableInfo(), false); + } else { + TableInfo tableInfo = new TableInfo(connectionSource, null, dataClass); + return addCreateTableStatements(connectionSource, tableInfo, false); + } + } + + /** + * Return an ordered collection of SQL statements that need to be run to create a table. To do the work of creating, + * you should call {@link #createTable}. + * + * @param connectionSource + * Our connect source which is used to get the database type, not to apply the creates. + * @param tableConfig + * Hand or spring wired table configuration. If null then the class must have {@link DatabaseField} + * annotations. + * @return The collection of table create statements. + */ + public static List getCreateTableStatements(ConnectionSource connectionSource, + DatabaseTableConfig tableConfig) throws SQLException { + Dao dao = DaoManager.createDao(connectionSource, tableConfig); + if (dao instanceof BaseDaoImpl) { + return addCreateTableStatements(connectionSource, ((BaseDaoImpl) dao).getTableInfo(), false); + } else { + tableConfig.extractFieldTypes(connectionSource); + TableInfo tableInfo = new TableInfo(connectionSource.getDatabaseType(), null, tableConfig); + return addCreateTableStatements(connectionSource, tableInfo, false); + } + } + + /** + * Issue the database statements to drop the table associated with a class. + * + *

+ * WARNING: This is [obviously] very destructive and is unrecoverable. + *

+ * + * @param connectionSource + * Associated connection source. + * @param dataClass + * The class for which a table will be dropped. + * @param ignoreErrors + * If set to true then try each statement regardless of {@link SQLException} thrown previously. + * @return The number of statements executed to do so. + */ + public static int dropTable(ConnectionSource connectionSource, Class dataClass, boolean ignoreErrors) + throws SQLException { + DatabaseType databaseType = connectionSource.getDatabaseType(); + Dao dao = DaoManager.createDao(connectionSource, dataClass); + if (dao instanceof BaseDaoImpl) { + return doDropTable(databaseType, connectionSource, ((BaseDaoImpl) dao).getTableInfo(), ignoreErrors); + } else { + TableInfo tableInfo = new TableInfo(connectionSource, null, dataClass); + return doDropTable(databaseType, connectionSource, tableInfo, ignoreErrors); + } + } + + /** + * Issue the database statements to drop the table associated with a table configuration. + * + *

+ * WARNING: This is [obviously] very destructive and is unrecoverable. + *

+ * + * @param connectionSource + * Associated connection source. + * @param tableConfig + * Hand or spring wired table configuration. If null then the class must have {@link DatabaseField} + * annotations. + * @param ignoreErrors + * If set to true then try each statement regardless of {@link SQLException} thrown previously. + * @return The number of statements executed to do so. + */ + public static int dropTable(ConnectionSource connectionSource, DatabaseTableConfig tableConfig, + boolean ignoreErrors) throws SQLException { + DatabaseType databaseType = connectionSource.getDatabaseType(); + Dao dao = DaoManager.createDao(connectionSource, tableConfig); + if (dao instanceof BaseDaoImpl) { + return doDropTable(databaseType, connectionSource, ((BaseDaoImpl) dao).getTableInfo(), ignoreErrors); + } else { + tableConfig.extractFieldTypes(connectionSource); + TableInfo tableInfo = new TableInfo(databaseType, null, tableConfig); + return doDropTable(databaseType, connectionSource, tableInfo, ignoreErrors); + } + } + + /** + * Clear all data out of the table. For certain database types and with large sized tables, which may take a long + * time. In some configurations, it may be faster to drop and re-create the table. + * + *

+ * WARNING: This is [obviously] very destructive and is unrecoverable. + *

+ */ + public static int clearTable(ConnectionSource connectionSource, Class dataClass) throws SQLException { + String tableName = DatabaseTableConfig.extractTableName(dataClass); + if (connectionSource.getDatabaseType().isEntityNamesMustBeUpCase()) { + tableName = tableName.toUpperCase(); + } + return clearTable(connectionSource, tableName); + } + + /** + * Clear all data out of the table. For certain database types and with large sized tables, which may take a long + * time. In some configurations, it may be faster to drop and re-create the table. + * + *

+ * WARNING: This is [obviously] very destructive and is unrecoverable. + *

+ */ + public static int clearTable(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) + throws SQLException { + return clearTable(connectionSource, tableConfig.getTableName()); + } + + private static int createTable(ConnectionSource connectionSource, Class dataClass, boolean ifNotExists) + throws SQLException { + Dao dao = DaoManager.createDao(connectionSource, dataClass); + if (dao instanceof BaseDaoImpl) { + return doCreateTable(connectionSource, ((BaseDaoImpl) dao).getTableInfo(), ifNotExists); + } else { + TableInfo tableInfo = new TableInfo(connectionSource, null, dataClass); + return doCreateTable(connectionSource, tableInfo, ifNotExists); + } + } + + private static int createTable(ConnectionSource connectionSource, DatabaseTableConfig tableConfig, + boolean ifNotExists) throws SQLException { + Dao dao = DaoManager.createDao(connectionSource, tableConfig); + if (dao instanceof BaseDaoImpl) { + return doCreateTable(connectionSource, ((BaseDaoImpl) dao).getTableInfo(), ifNotExists); + } else { + tableConfig.extractFieldTypes(connectionSource); + TableInfo tableInfo = new TableInfo(connectionSource.getDatabaseType(), null, tableConfig); + return doCreateTable(connectionSource, tableInfo, ifNotExists); + } + } + + private static int clearTable(ConnectionSource connectionSource, String tableName) throws SQLException { + DatabaseType databaseType = connectionSource.getDatabaseType(); + StringBuilder sb = new StringBuilder(48); + if (databaseType.isTruncateSupported()) { + sb.append("TRUNCATE TABLE "); + } else { + sb.append("DELETE FROM "); + } + databaseType.appendEscapedEntityName(sb, tableName); + String statement = sb.toString(); + logger.info("clearing table '{}' with '{}", tableName, statement); + CompiledStatement compiledStmt = null; + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + compiledStmt = + connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + return compiledStmt.runExecute(); + } finally { + IOUtils.closeThrowSqlException(compiledStmt, "compiled statement"); + connectionSource.releaseConnection(connection); + } + } + + private static int doDropTable(DatabaseType databaseType, ConnectionSource connectionSource, + TableInfo tableInfo, boolean ignoreErrors) throws SQLException { + logger.info("dropping table '{}'", tableInfo.getTableName()); + List statements = new ArrayList(); + addDropIndexStatements(databaseType, tableInfo, statements); + addDropTableStatements(databaseType, tableInfo, statements); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + return doStatements(connection, "drop", statements, ignoreErrors, + databaseType.isCreateTableReturnsNegative(), false); + } finally { + connectionSource.releaseConnection(connection); + } + } + + private static void addDropIndexStatements(DatabaseType databaseType, TableInfo tableInfo, + List statements) { + // run through and look for index annotations + Set indexSet = new HashSet(); + for (FieldType fieldType : tableInfo.getFieldTypes()) { + String indexName = fieldType.getIndexName(); + if (indexName != null) { + indexSet.add(indexName); + } + String uniqueIndexName = fieldType.getUniqueIndexName(); + if (uniqueIndexName != null) { + indexSet.add(uniqueIndexName); + } + } + + StringBuilder sb = new StringBuilder(48); + for (String indexName : indexSet) { + logger.info("dropping index '{}' for table '{}", indexName, tableInfo.getTableName()); + sb.append("DROP INDEX "); + databaseType.appendEscapedEntityName(sb, indexName); + statements.add(sb.toString()); + sb.setLength(0); + } + } + + /** + * Generate and return the list of statements to create a database table and any associated features. + */ + private static void addCreateTableStatements(DatabaseType databaseType, TableInfo tableInfo, + List statements, List queriesAfter, boolean ifNotExists) throws SQLException { + StringBuilder sb = new StringBuilder(256); + sb.append("CREATE TABLE "); + if (ifNotExists && databaseType.isCreateIfNotExistsSupported()) { + sb.append("IF NOT EXISTS "); + } + databaseType.appendEscapedEntityName(sb, tableInfo.getTableName()); + sb.append(" ("); + List additionalArgs = new ArrayList(); + List statementsBefore = new ArrayList(); + List statementsAfter = new ArrayList(); + // our statement will be set here later + boolean first = true; + for (FieldType fieldType : tableInfo.getFieldTypes()) { + // skip foreign collections + if (fieldType.isForeignCollection()) { + continue; + } else if (first) { + first = false; + } else { + sb.append(", "); + } + String columnDefinition = fieldType.getColumnDefinition(); + if (columnDefinition == null) { + // we have to call back to the database type for the specific create syntax + databaseType.appendColumnArg(tableInfo.getTableName(), sb, fieldType, additionalArgs, statementsBefore, + statementsAfter, queriesAfter); + } else { + // hand defined field + databaseType.appendEscapedEntityName(sb, fieldType.getColumnName()); + sb.append(' ').append(columnDefinition).append(' '); + } + } + // add any sql that sets any primary key fields + databaseType.addPrimaryKeySql(tableInfo.getFieldTypes(), additionalArgs, statementsBefore, statementsAfter, + queriesAfter); + // add any sql that sets any unique fields + databaseType.addUniqueComboSql(tableInfo.getFieldTypes(), additionalArgs, statementsBefore, statementsAfter, + queriesAfter); + for (String arg : additionalArgs) { + // we will have spat out one argument already so we don't have to do the first dance + sb.append(", ").append(arg); + } + sb.append(") "); + databaseType.appendCreateTableSuffix(sb); + statements.addAll(statementsBefore); + statements.add(sb.toString()); + statements.addAll(statementsAfter); + addCreateIndexStatements(databaseType, tableInfo, statements, ifNotExists, false); + addCreateIndexStatements(databaseType, tableInfo, statements, ifNotExists, true); + } + + private static void addCreateIndexStatements(DatabaseType databaseType, TableInfo tableInfo, + List statements, boolean ifNotExists, boolean unique) { + // run through and look for index annotations + Map> indexMap = new HashMap>(); + for (FieldType fieldType : tableInfo.getFieldTypes()) { + String indexName; + if (unique) { + indexName = fieldType.getUniqueIndexName(); + } else { + indexName = fieldType.getIndexName(); + } + if (indexName == null) { + continue; + } + + List columnList = indexMap.get(indexName); + if (columnList == null) { + columnList = new ArrayList(); + indexMap.put(indexName, columnList); + } + columnList.add(fieldType.getColumnName()); + } + + StringBuilder sb = new StringBuilder(128); + for (Map.Entry> indexEntry : indexMap.entrySet()) { + logger.info("creating index '{}' for table '{}", indexEntry.getKey(), tableInfo.getTableName()); + sb.append("CREATE "); + if (unique) { + sb.append("UNIQUE "); + } + sb.append("INDEX "); + if (ifNotExists && databaseType.isCreateIndexIfNotExistsSupported()) { + sb.append("IF NOT EXISTS "); + } + databaseType.appendEscapedEntityName(sb, indexEntry.getKey()); + sb.append(" ON "); + databaseType.appendEscapedEntityName(sb, tableInfo.getTableName()); + sb.append(" ( "); + boolean first = true; + for (String columnName : indexEntry.getValue()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + databaseType.appendEscapedEntityName(sb, columnName); + } + sb.append(" )"); + statements.add(sb.toString()); + sb.setLength(0); + } + } + + /** + * Generate and return the list of statements to drop a database table. + */ + private static void addDropTableStatements(DatabaseType databaseType, TableInfo tableInfo, + List statements) { + List statementsBefore = new ArrayList(); + List statementsAfter = new ArrayList(); + for (FieldType fieldType : tableInfo.getFieldTypes()) { + databaseType.dropColumnArg(fieldType, statementsBefore, statementsAfter); + } + StringBuilder sb = new StringBuilder(64); + sb.append("DROP TABLE "); + databaseType.appendEscapedEntityName(sb, tableInfo.getTableName()); + sb.append(' '); + statements.addAll(statementsBefore); + statements.add(sb.toString()); + statements.addAll(statementsAfter); + } + + private static int doCreateTable(ConnectionSource connectionSource, TableInfo tableInfo, + boolean ifNotExists) throws SQLException { + DatabaseType databaseType = connectionSource.getDatabaseType(); + logger.info("creating table '{}'", tableInfo.getTableName()); + List statements = new ArrayList(); + List queriesAfter = new ArrayList(); + addCreateTableStatements(databaseType, tableInfo, statements, queriesAfter, ifNotExists); + DatabaseConnection connection = connectionSource.getReadWriteConnection(); + try { + int stmtC = + doStatements(connection, "create", statements, false, databaseType.isCreateTableReturnsNegative(), + databaseType.isCreateTableReturnsZero()); + stmtC += doCreateTestQueries(connection, databaseType, queriesAfter); + return stmtC; + } finally { + connectionSource.releaseConnection(connection); + } + } + + private static int doStatements(DatabaseConnection connection, String label, Collection statements, + boolean ignoreErrors, boolean returnsNegative, boolean expectingZero) throws SQLException { + int stmtC = 0; + for (String statement : statements) { + int rowC = 0; + CompiledStatement compiledStmt = null; + try { + compiledStmt = + connection.compileStatement(statement, StatementType.EXECUTE, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + rowC = compiledStmt.runExecute(); + logger.info("executed {} table statement changed {} rows: {}", label, rowC, statement); + } catch (SQLException e) { + if (ignoreErrors) { + logger.info("ignoring {} error '{}' for statement: {}", label, e, statement); + } else { + throw SqlExceptionUtil.create("SQL statement failed: " + statement, e); + } + } finally { + IOUtils.closeThrowSqlException(compiledStmt, "compiled statement"); + } + // sanity check + if (rowC < 0) { + if (!returnsNegative) { + throw new SQLException("SQL statement " + statement + " updated " + rowC + + " rows, we were expecting >= 0"); + } + } else if (rowC > 0 && expectingZero) { + throw new SQLException("SQL statement updated " + rowC + " rows, we were expecting == 0: " + statement); + } + stmtC++; + } + return stmtC; + } + + private static int doCreateTestQueries(DatabaseConnection connection, DatabaseType databaseType, + List queriesAfter) throws SQLException { + int stmtC = 0; + // now execute any test queries which test the newly created table + for (String query : queriesAfter) { + CompiledStatement compiledStmt = null; + try { + compiledStmt = + connection.compileStatement(query, StatementType.SELECT, noFieldTypes, + DatabaseConnection.DEFAULT_RESULT_FLAGS); + // we don't care about an object cache here + DatabaseResults results = compiledStmt.runQuery(null); + int rowC = 0; + // count the results + for (boolean isThereMore = results.first(); isThereMore; isThereMore = results.next()) { + rowC++; + } + logger.info("executing create table after-query got {} results: {}", rowC, query); + } catch (SQLException e) { + // we do this to make sure that the statement is in the exception + throw SqlExceptionUtil.create("executing create table after-query failed: " + query, e); + } finally { + // result set is closed by the statement being closed + IOUtils.closeThrowSqlException(compiledStmt, "compiled statement"); + } + stmtC++; + } + return stmtC; + } + + private static List addCreateTableStatements(ConnectionSource connectionSource, + TableInfo tableInfo, boolean ifNotExists) throws SQLException { + List statements = new ArrayList(); + List queriesAfter = new ArrayList(); + addCreateTableStatements(connectionSource.getDatabaseType(), tableInfo, statements, queriesAfter, ifNotExists); + return statements; + } +} From 6e08d65d7721baf0322f3c7e2104c95bb52e29f5 Mon Sep 17 00:00:00 2001 From: Allison Bojarski Date: Wed, 22 Jul 2015 23:21:42 -0400 Subject: [PATCH 3/3] replaced hardcoded hash map with database table meme template --- MemeProject/app/src/main/AndroidManifest.xml | 2 +- .../c4q/nyc/memeproject/MainActivity.java | 10 +- .../{MemeList.java => MemeListActivity.java} | 65 ++++++---- .../memeproject/database/DatabaseHelper.java | 116 ++++++++++++++++++ .../memeproject/database/MemeTemplate.java | 33 +++++ .../main/res/layout/activity_meme_list.xml | 2 +- .../app/src/main/res/menu/menu_meme_list2.xml | 2 +- 7 files changed, 202 insertions(+), 28 deletions(-) rename MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/{MemeList.java => MemeListActivity.java} (56%) create mode 100644 MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/DatabaseHelper.java create mode 100644 MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/MemeTemplate.java diff --git a/MemeProject/app/src/main/AndroidManifest.xml b/MemeProject/app/src/main/AndroidManifest.xml index 1f734f0..3911691 100644 --- a/MemeProject/app/src/main/AndroidManifest.xml +++ b/MemeProject/app/src/main/AndroidManifest.xml @@ -30,7 +30,7 @@ - diff --git a/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MainActivity.java b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MainActivity.java index d1ee61a..734944f 100644 --- a/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MainActivity.java +++ b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MainActivity.java @@ -1,5 +1,4 @@ package madelyntav.c4q.nyc.memeproject; -import android.content.ContentResolver; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; @@ -10,8 +9,8 @@ import android.view.View; import android.widget.ImageView; import android.widget.Toast; + import java.io.ByteArrayOutputStream; -import java.io.IOException; public class MainActivity extends ActionBarActivity { @@ -28,8 +27,11 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + } + public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || @@ -55,7 +57,7 @@ public void takePic(View v) { } public void chooseMeme(View view){ - Intent popularMemeIntent = new Intent(MainActivity.this, MemeList.class); + Intent popularMemeIntent = new Intent(MainActivity.this, MemeListActivity.class); startActivity(popularMemeIntent); } @@ -123,4 +125,6 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } + + } \ No newline at end of file diff --git a/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MemeList.java b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MemeListActivity.java similarity index 56% rename from MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MemeList.java rename to MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MemeListActivity.java index 4cff6ef..bdc174c 100644 --- a/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MemeList.java +++ b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/MemeListActivity.java @@ -10,42 +10,43 @@ import android.widget.ListView; import android.widget.Toast; -import java.io.InputStream; +import com.j256.ormlite.android.apptools.OpenHelperManager; +import com.j256.ormlite.dao.Dao; + +import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; + +import madelyntav.c4q.nyc.memeproject.database.DatabaseHelper; +import madelyntav.c4q.nyc.memeproject.database.MemeTemplate; /** * Created by kadeemmaragh on 6/5/15. */ -public class MemeList extends Activity { +public class MemeListActivity extends Activity { ListView listView; HashMap memePairs; private Uri uri; + private DatabaseHelper databaseHelper = null; + private Dao memeTemplateDao; + private List memeTemplateList; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_meme_list); + try { + memeTemplateDao = getDbHelper().getMemeTemplateDao(); + memeTemplateList = memeTemplateDao.queryForAll(); + } catch (SQLException e) { + e.printStackTrace(); + } listView = (ListView) findViewById(R.id.listView); - memePairs = new HashMap<>(); - memePairs.put(R.drawable.actual_advice_mallard, "Actual Advice Mallard"); - memePairs.put(R.drawable.but_thats_none_of_my_business, "But That's None Of My Business"); - memePairs.put(R.drawable.creepy_condescending_wonka, "Creepy Condescending Wonka"); - memePairs.put(R.drawable.futurama_fry, "Skeptical Fry"); - memePairs.put(R.drawable.good_guy_greg, "Good Guy Greg"); - memePairs.put(R.drawable.liam_neeson_taken, "Liam Neeson Taken"); - memePairs.put(R.drawable.one_does_not_simply, "One Does Not Simply"); - memePairs.put(R.drawable.scumbag_steve, "Scumbag Steve"); - memePairs.put(R.drawable.shut_up_and_take_my_money_fry, "Shut Up And Take My Money"); - memePairs.put(R.drawable.ten_guy, "Ten Guy"); - memePairs.put(R.drawable.the_most_interesting_man_in_the_world, "The Most Interesting Man In The World"); - memePairs.put(R.drawable.third_world_skeptical_kid, "Third World Skeptical Kid"); - memePairs.put(R.drawable.unhelpful_high_school_teacher, "Unhelpful High School Teacher"); - memePairs.put(R.drawable.yao_ming, "Yao Ming"); - memePairs.put(R.drawable.you_the_real_mvp, "You The Real MVP"); final ArrayList memeImages = new ArrayList(); @@ -59,7 +60,7 @@ public void onCreate(Bundle savedInstanceState) { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - Intent intent = new Intent(MemeList.this, EditPhoto.class); + Intent intent = new Intent(MemeListActivity.this, EditPhoto.class); uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getResources().getResourcePackageName(memeImages.get(position)) + '/' + getResources().getResourceTypeName(memeImages.get(position)) + '/' + getResources().getResourceEntryName(memeImages.get(position))); @@ -74,16 +75,36 @@ public void onItemClick(AdapterView parent, View view, int position, long id) + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (databaseHelper != null) { + OpenHelperManager.releaseHelper(); + databaseHelper = null; + } } public void addItemsToArrays(ArrayList images, ArrayList titles) { int position = 0; - for (Integer image : memePairs.keySet()) { - images.add(position, image); - titles.add(position, memePairs.get(image)); + for (MemeTemplate memeTemplate : memeTemplateList) { + + String imageName = memeTemplate.imageName; + int imageDrawable = DatabaseHelper.getImageDrawable(imageName) ; + images.add(position, imageDrawable); + titles.add(position, memeTemplate.title); position++; } } + + private DatabaseHelper getDbHelper() { + if (databaseHelper == null) { + databaseHelper = OpenHelperManager.getHelper(this, DatabaseHelper.class); + } + return databaseHelper; + } + } diff --git a/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/DatabaseHelper.java b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/DatabaseHelper.java new file mode 100644 index 0000000..3272371 --- /dev/null +++ b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/DatabaseHelper.java @@ -0,0 +1,116 @@ +package madelyntav.c4q.nyc.memeproject.database; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.TableUtils; + +import java.io.File; +import java.io.InputStream; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import madelyntav.c4q.nyc.memeproject.R; + +/** + * Created by c4q-Allison on 7/22/15. + */ +public class DatabaseHelper extends OrmLiteSqliteOpenHelper { + public static final String DB_NAMES = "memes.sqlite"; + public static final int DB_VERSION = 1; + private Dao memeTemplateDao; + + + public DatabaseHelper(Context context) { + super(context, DB_NAMES, null, DB_VERSION); + } + + + @Override + public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) { + try { + TableUtils.createTable(connectionSource, MemeTemplate.class); + loadTemplateData(); + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + @Override + public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) { + + } + + public Dao getMemeTemplateDao() throws SQLException { + if (memeTemplateDao == null) { + memeTemplateDao = getDao(MemeTemplate.class); + } + return memeTemplateDao; + + } + + public static int getImageDrawable(String imageName) { + + //default to R.drawable.good_guy_greg; + + if (imageName == null) { + return R.drawable.good_guy_greg; + } + + if (imageName.equals("actual_advice_mallard")) return R.drawable.actual_advice_mallard; + if (imageName.equals("but_thats_none_of_my_business")) return R.drawable.but_thats_none_of_my_business; + if (imageName.equals("creepy_condescending_wonka")) return R.drawable.creepy_condescending_wonka; + if (imageName.equals("futurama_fry")) return R.drawable.futurama_fry; + if (imageName.equals("good_guy_greg")) return R.drawable.good_guy_greg; + if (imageName.equals("liam_neeson_taken")) return R.drawable.liam_neeson_taken; + if (imageName.equals("one_does_not_simply")) return R.drawable.one_does_not_simply; + if (imageName.equals("scumbag_steve")) return R.drawable.scumbag_steve; + if (imageName.equals("shut_up_and_take_my_money_fry")) return R.drawable.shut_up_and_take_my_money_fry; + if (imageName.equals("ten_guy")) return R.drawable.ten_guy; + if (imageName.equals("the_most_interesting_man_in_the_world")) return R.drawable.the_most_interesting_man_in_the_world; + if (imageName.equals("unhelpful_high_school_teacher")) return R.drawable.unhelpful_high_school_teacher; + if (imageName.equals("yao_ming")) return R.drawable.yao_ming; + if (imageName.equals("you_the_real_mvp")) return R.drawable.you_the_real_mvp; + + //default to R.drawable.good_guy_greg; + + return R.drawable.good_guy_greg; + + } + + + protected void loadTemplateData() { + List memeTemplateList = new ArrayList(); + MemeTemplate memeTemplate = new MemeTemplate("actual_advice_mallard", "Actual Advice Mallard"); + memeTemplateList.add(memeTemplate); + memeTemplateList.add(new MemeTemplate("but_thats_none_of_my_business", "But That's None Of My Business")); + memeTemplateList.add(new MemeTemplate("creepy_condescending_wonka", "Creepy Condescending Wonka")); + memeTemplateList.add(new MemeTemplate("futurama_fry", "Skeptical Fry")); + memeTemplateList.add(new MemeTemplate("good_guy_greg", "Good Guy Greg")); + memeTemplateList.add(new MemeTemplate("liam_neeson_taken", "Liam Neeson Taken")); + memeTemplateList.add(new MemeTemplate("one_does_not_simply", "One Does Not Simply")); + memeTemplateList.add(new MemeTemplate("scumbag_steve", "Scumbag Steve")); + memeTemplateList.add(new MemeTemplate("shut_up_and_take_my_money_fry", "Shut Up And Take My Money")); + memeTemplateList.add(new MemeTemplate("ten_guy", "Ten Guy")); + memeTemplateList.add(new MemeTemplate("the_most_interesting_man_in_the_world", "The Most Interesting Man In The World")); + memeTemplateList.add(new MemeTemplate("unhelpful_high_school_teacher", "Unhelpful High School Teacher")); + memeTemplateList.add(new MemeTemplate("yao_ming", "Yao Ming")); + memeTemplateList.add(new MemeTemplate("you_the_real_mvp", "You The Real MVP")); + + + try { + Dao memeTemplateDao = getMemeTemplateDao(); + memeTemplateDao.create(memeTemplateList); + } catch (SQLException e) { + e.printStackTrace(); + } + + + } +} diff --git a/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/MemeTemplate.java b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/MemeTemplate.java new file mode 100644 index 0000000..fbe33c0 --- /dev/null +++ b/MemeProject/app/src/main/java/madelyntav/c4q/nyc/memeproject/database/MemeTemplate.java @@ -0,0 +1,33 @@ +package madelyntav.c4q.nyc.memeproject.database; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +/** + * Created by c4q-Allison on 7/22/15. + */ +@DatabaseTable(tableName = "meme_template") +public class MemeTemplate { + + + + @DatabaseField(id = true, columnName = "image_name") + public String imageName; + + @DatabaseField(columnName = "title") + public String title; + + public MemeTemplate(String imageName, String title) { + this.imageName = imageName; + this.title = title; + } + public MemeTemplate() { + + } + + + + + + +} diff --git a/MemeProject/app/src/main/res/layout/activity_meme_list.xml b/MemeProject/app/src/main/res/layout/activity_meme_list.xml index bec8ac7..c36e1d1 100644 --- a/MemeProject/app/src/main/res/layout/activity_meme_list.xml +++ b/MemeProject/app/src/main/res/layout/activity_meme_list.xml @@ -6,7 +6,7 @@ android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" - tools:context="madelyntav.c4q.nyc.memeproject.MemeList" + tools:context="madelyntav.c4q.nyc.memeproject.MemeListActivity" android:orientation="vertical" android:background="#CCC"> diff --git a/MemeProject/app/src/main/res/menu/menu_meme_list2.xml b/MemeProject/app/src/main/res/menu/menu_meme_list2.xml index 3a46850..080dfa7 100644 --- a/MemeProject/app/src/main/res/menu/menu_meme_list2.xml +++ b/MemeProject/app/src/main/res/menu/menu_meme_list2.xml @@ -1,7 +1,7 @@ + tools:context="madelyntav.c4q.nyc.memeproject.MemeListActivity">