From b4a33cba39afb5d3d2b39a00c1424aac8f849e32 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Sat, 14 Apr 2018 10:22:31 -0500 Subject: [PATCH] Add ActiveAndroid source code to our tree and remove content providers ActiveAndroid is not actively maintained anymore and contains code related to that Content Providers that makes the application crash on Android Oreo. --- .../java/com/activeandroid/ActiveAndroid.java | 86 ++++ .../main/java/com/activeandroid/Cache.java | 158 +++++++ .../java/com/activeandroid/Configuration.java | 318 ++++++++++++++ .../com/activeandroid/DatabaseHelper.java | 257 +++++++++++ .../main/java/com/activeandroid/Model.java | 314 ++++++++++++++ .../java/com/activeandroid/ModelInfo.java | 209 +++++++++ .../java/com/activeandroid/TableInfo.java | 124 ++++++ .../com/activeandroid/annotation/Column.java | 110 +++++ .../com/activeandroid/annotation/Table.java | 31 ++ .../java/com/activeandroid/query/Delete.java | 33 ++ .../java/com/activeandroid/query/From.java | 344 +++++++++++++++ .../java/com/activeandroid/query/Join.java | 94 ++++ .../java/com/activeandroid/query/Select.java | 93 ++++ .../java/com/activeandroid/query/Set.java | 103 +++++ .../java/com/activeandroid/query/Sqlable.java | 21 + .../java/com/activeandroid/query/Update.java | 50 +++ .../serializer/BigDecimalSerializer.java | 29 ++ .../serializer/CalendarSerializer.java | 40 ++ .../serializer/FileSerializer.java | 46 ++ .../serializer/SqlDateSerializer.java | 45 ++ .../serializer/TypeSerializer.java | 27 ++ .../serializer/UUIDSerializer.java | 29 ++ .../serializer/UtilDateSerializer.java | 45 ++ .../java/com/activeandroid/util/IOUtils.java | 71 +++ .../main/java/com/activeandroid/util/Log.java | 196 +++++++++ .../util/NaturalOrderComparator.java | 141 ++++++ .../activeandroid/util/ReflectionUtils.java | 110 +++++ .../com/activeandroid/util/SQLiteUtils.java | 406 ++++++++++++++++++ .../com/activeandroid/util/SqlParser.java | 110 +++++ .../com/activeandroid/util/Tokenizer.java | 76 ++++ .../activeandroid/widget/ModelAdapter.java | 57 +++ 31 files changed, 3773 insertions(+) create mode 100644 app/src/main/java/com/activeandroid/ActiveAndroid.java create mode 100644 app/src/main/java/com/activeandroid/Cache.java create mode 100644 app/src/main/java/com/activeandroid/Configuration.java create mode 100644 app/src/main/java/com/activeandroid/DatabaseHelper.java create mode 100644 app/src/main/java/com/activeandroid/Model.java create mode 100644 app/src/main/java/com/activeandroid/ModelInfo.java create mode 100644 app/src/main/java/com/activeandroid/TableInfo.java create mode 100644 app/src/main/java/com/activeandroid/annotation/Column.java create mode 100644 app/src/main/java/com/activeandroid/annotation/Table.java create mode 100644 app/src/main/java/com/activeandroid/query/Delete.java create mode 100644 app/src/main/java/com/activeandroid/query/From.java create mode 100644 app/src/main/java/com/activeandroid/query/Join.java create mode 100644 app/src/main/java/com/activeandroid/query/Select.java create mode 100644 app/src/main/java/com/activeandroid/query/Set.java create mode 100644 app/src/main/java/com/activeandroid/query/Sqlable.java create mode 100644 app/src/main/java/com/activeandroid/query/Update.java create mode 100644 app/src/main/java/com/activeandroid/serializer/BigDecimalSerializer.java create mode 100644 app/src/main/java/com/activeandroid/serializer/CalendarSerializer.java create mode 100644 app/src/main/java/com/activeandroid/serializer/FileSerializer.java create mode 100644 app/src/main/java/com/activeandroid/serializer/SqlDateSerializer.java create mode 100644 app/src/main/java/com/activeandroid/serializer/TypeSerializer.java create mode 100644 app/src/main/java/com/activeandroid/serializer/UUIDSerializer.java create mode 100644 app/src/main/java/com/activeandroid/serializer/UtilDateSerializer.java create mode 100644 app/src/main/java/com/activeandroid/util/IOUtils.java create mode 100644 app/src/main/java/com/activeandroid/util/Log.java create mode 100644 app/src/main/java/com/activeandroid/util/NaturalOrderComparator.java create mode 100644 app/src/main/java/com/activeandroid/util/ReflectionUtils.java create mode 100644 app/src/main/java/com/activeandroid/util/SQLiteUtils.java create mode 100644 app/src/main/java/com/activeandroid/util/SqlParser.java create mode 100644 app/src/main/java/com/activeandroid/util/Tokenizer.java create mode 100644 app/src/main/java/com/activeandroid/widget/ModelAdapter.java diff --git a/app/src/main/java/com/activeandroid/ActiveAndroid.java b/app/src/main/java/com/activeandroid/ActiveAndroid.java new file mode 100644 index 000000000..c58c8efd8 --- /dev/null +++ b/app/src/main/java/com/activeandroid/ActiveAndroid.java @@ -0,0 +1,86 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import com.activeandroid.util.Log; + +public final class ActiveAndroid { + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public static void initialize(Context context) { + initialize(new Configuration.Builder(context).create()); + } + + public static void initialize(Configuration configuration) { + initialize(configuration, false); + } + + public static void initialize(Context context, boolean loggingEnabled) { + initialize(new Configuration.Builder(context).create(), loggingEnabled); + } + + public static void initialize(Configuration configuration, boolean loggingEnabled) { + // Set logging enabled first + setLoggingEnabled(loggingEnabled); + Cache.initialize(configuration); + } + + public static void clearCache() { + Cache.clear(); + } + + public static void dispose() { + Cache.dispose(); + } + + public static void setLoggingEnabled(boolean enabled) { + Log.setEnabled(enabled); + } + + public static SQLiteDatabase getDatabase() { + return Cache.openDatabase(); + } + + public static void beginTransaction() { + Cache.openDatabase().beginTransaction(); + } + + public static void endTransaction() { + Cache.openDatabase().endTransaction(); + } + + public static void setTransactionSuccessful() { + Cache.openDatabase().setTransactionSuccessful(); + } + + public static boolean inTransaction() { + return Cache.openDatabase().inTransaction(); + } + + public static void execSQL(String sql) { + Cache.openDatabase().execSQL(sql); + } + + public static void execSQL(String sql, Object[] bindArgs) { + Cache.openDatabase().execSQL(sql, bindArgs); + } +} diff --git a/app/src/main/java/com/activeandroid/Cache.java b/app/src/main/java/com/activeandroid/Cache.java new file mode 100644 index 000000000..6495e3790 --- /dev/null +++ b/app/src/main/java/com/activeandroid/Cache.java @@ -0,0 +1,158 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Collection; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.support.v4.util.LruCache; + +import com.activeandroid.serializer.TypeSerializer; +import com.activeandroid.util.Log; + +public final class Cache { + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC CONSTANTS + ////////////////////////////////////////////////////////////////////////////////////// + + public static final int DEFAULT_CACHE_SIZE = 1024; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private static Context sContext; + + private static ModelInfo sModelInfo; + private static DatabaseHelper sDatabaseHelper; + + private static LruCache sEntities; + + private static boolean sIsInitialized = false; + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + private Cache() { + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public static synchronized void initialize(Configuration configuration) { + if (sIsInitialized) { + Log.v("ActiveAndroid already initialized."); + return; + } + + sContext = configuration.getContext(); + sModelInfo = new ModelInfo(configuration); + sDatabaseHelper = new DatabaseHelper(configuration); + + // TODO: It would be nice to override sizeOf here and calculate the memory + // actually used, however at this point it seems like the reflection + // required would be too costly to be of any benefit. We'll just set a max + // object size instead. + sEntities = new LruCache(configuration.getCacheSize()); + + openDatabase(); + + sIsInitialized = true; + + Log.v("ActiveAndroid initialized successfully."); + } + + public static synchronized void clear() { + sEntities.evictAll(); + Log.v("Cache cleared."); + } + + public static synchronized void dispose() { + closeDatabase(); + + sEntities = null; + sModelInfo = null; + sDatabaseHelper = null; + + sIsInitialized = false; + + Log.v("ActiveAndroid disposed. Call initialize to use library."); + } + + // Database access + + public static boolean isInitialized() { + return sIsInitialized; + } + + public static synchronized SQLiteDatabase openDatabase() { + return sDatabaseHelper.getWritableDatabase(); + } + + public static synchronized void closeDatabase() { + sDatabaseHelper.close(); + } + + // Context access + + public static Context getContext() { + return sContext; + } + + // Entity cache + + public static String getIdentifier(Class type, Long id) { + return getTableName(type) + "@" + id; + } + + public static String getIdentifier(Model entity) { + return getIdentifier(entity.getClass(), entity.getId()); + } + + public static synchronized void addEntity(Model entity) { + sEntities.put(getIdentifier(entity), entity); + } + + public static synchronized Model getEntity(Class type, long id) { + return sEntities.get(getIdentifier(type, id)); + } + + public static synchronized void removeEntity(Model entity) { + sEntities.remove(getIdentifier(entity)); + } + + // Model cache + + public static synchronized Collection getTableInfos() { + return sModelInfo.getTableInfos(); + } + + public static synchronized TableInfo getTableInfo(Class type) { + return sModelInfo.getTableInfo(type); + } + + public static synchronized TypeSerializer getParserForType(Class type) { + return sModelInfo.getTypeSerializer(type); + } + + public static synchronized String getTableName(Class type) { + return sModelInfo.getTableInfo(type).getTableName(); + } +} diff --git a/app/src/main/java/com/activeandroid/Configuration.java b/app/src/main/java/com/activeandroid/Configuration.java new file mode 100644 index 000000000..b197d2234 --- /dev/null +++ b/app/src/main/java/com/activeandroid/Configuration.java @@ -0,0 +1,318 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import android.content.Context; + +import com.activeandroid.serializer.TypeSerializer; +import com.activeandroid.util.Log; +import com.activeandroid.util.ReflectionUtils; + +public class Configuration { + + public final static String SQL_PARSER_LEGACY = "legacy"; + public final static String SQL_PARSER_DELIMITED = "delimited"; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private Context mContext; + private String mDatabaseName; + private int mDatabaseVersion; + private String mSqlParser; + private List> mModelClasses; + private List> mTypeSerializers; + private int mCacheSize; + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + private Configuration(Context context) { + mContext = context; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public Context getContext() { + return mContext; + } + + public String getDatabaseName() { + return mDatabaseName; + } + + public int getDatabaseVersion() { + return mDatabaseVersion; + } + + public String getSqlParser() { + return mSqlParser; + } + + public List> getModelClasses() { + return mModelClasses; + } + + public List> getTypeSerializers() { + return mTypeSerializers; + } + + public int getCacheSize() { + return mCacheSize; + } + + public boolean isValid() { + return mModelClasses != null && mModelClasses.size() > 0; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // INNER CLASSES + ////////////////////////////////////////////////////////////////////////////////////// + + public static class Builder { + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE CONSTANTS + ////////////////////////////////////////////////////////////////////////////////////// + + private static final String AA_DB_NAME = "AA_DB_NAME"; + private static final String AA_DB_VERSION = "AA_DB_VERSION"; + private final static String AA_MODELS = "AA_MODELS"; + private final static String AA_SERIALIZERS = "AA_SERIALIZERS"; + private final static String AA_SQL_PARSER = "AA_SQL_PARSER"; + + private static final int DEFAULT_CACHE_SIZE = 1024; + private static final String DEFAULT_DB_NAME = "Application.db"; + private static final String DEFAULT_SQL_PARSER = SQL_PARSER_LEGACY; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private Context mContext; + + private Integer mCacheSize; + private String mDatabaseName; + private Integer mDatabaseVersion; + private String mSqlParser; + private List> mModelClasses; + private List> mTypeSerializers; + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + public Builder(Context context) { + mContext = context.getApplicationContext(); + mCacheSize = DEFAULT_CACHE_SIZE; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public Builder setCacheSize(int cacheSize) { + mCacheSize = cacheSize; + return this; + } + + public Builder setDatabaseName(String databaseName) { + mDatabaseName = databaseName; + return this; + } + + public Builder setDatabaseVersion(int databaseVersion) { + mDatabaseVersion = databaseVersion; + return this; + } + + public Builder setSqlParser(String sqlParser) { + mSqlParser = sqlParser; + return this; + } + + public Builder addModelClass(Class modelClass) { + if (mModelClasses == null) { + mModelClasses = new ArrayList>(); + } + + mModelClasses.add(modelClass); + return this; + } + + public Builder addModelClasses(Class... modelClasses) { + if (mModelClasses == null) { + mModelClasses = new ArrayList>(); + } + + mModelClasses.addAll(Arrays.asList(modelClasses)); + return this; + } + + public Builder setModelClasses(Class... modelClasses) { + mModelClasses = Arrays.asList(modelClasses); + return this; + } + + public Builder addTypeSerializer(Class typeSerializer) { + if (mTypeSerializers == null) { + mTypeSerializers = new ArrayList>(); + } + + mTypeSerializers.add(typeSerializer); + return this; + } + + public Builder addTypeSerializers(Class... typeSerializers) { + if (mTypeSerializers == null) { + mTypeSerializers = new ArrayList>(); + } + + mTypeSerializers.addAll(Arrays.asList(typeSerializers)); + return this; + } + + public Builder setTypeSerializers(Class... typeSerializers) { + mTypeSerializers = Arrays.asList(typeSerializers); + return this; + } + + public Configuration create() { + Configuration configuration = new Configuration(mContext); + configuration.mCacheSize = mCacheSize; + + // Get database name from meta-data + if (mDatabaseName != null) { + configuration.mDatabaseName = mDatabaseName; + } else { + configuration.mDatabaseName = getMetaDataDatabaseNameOrDefault(); + } + + // Get database version from meta-data + if (mDatabaseVersion != null) { + configuration.mDatabaseVersion = mDatabaseVersion; + } else { + configuration.mDatabaseVersion = getMetaDataDatabaseVersionOrDefault(); + } + + // Get SQL parser from meta-data + if (mSqlParser != null) { + configuration.mSqlParser = mSqlParser; + } else { + configuration.mSqlParser = getMetaDataSqlParserOrDefault(); + } + + // Get model classes from meta-data + if (mModelClasses != null) { + configuration.mModelClasses = mModelClasses; + } else { + final String modelList = ReflectionUtils.getMetaData(mContext, AA_MODELS); + if (modelList != null) { + configuration.mModelClasses = loadModelList(modelList.split(",")); + } + } + + // Get type serializer classes from meta-data + if (mTypeSerializers != null) { + configuration.mTypeSerializers = mTypeSerializers; + } else { + final String serializerList = ReflectionUtils.getMetaData(mContext, AA_SERIALIZERS); + if (serializerList != null) { + configuration.mTypeSerializers = loadSerializerList(serializerList.split(",")); + } + } + + return configuration; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + // Meta-data methods + + private String getMetaDataDatabaseNameOrDefault() { + String aaName = ReflectionUtils.getMetaData(mContext, AA_DB_NAME); + if (aaName == null) { + aaName = DEFAULT_DB_NAME; + } + + return aaName; + } + + private int getMetaDataDatabaseVersionOrDefault() { + Integer aaVersion = ReflectionUtils.getMetaData(mContext, AA_DB_VERSION); + if (aaVersion == null || aaVersion == 0) { + aaVersion = 1; + } + + return aaVersion; + } + + private String getMetaDataSqlParserOrDefault() { + final String mode = ReflectionUtils.getMetaData(mContext, AA_SQL_PARSER); + if (mode == null) { + return DEFAULT_SQL_PARSER; + } + return mode; + } + + private List> loadModelList(String[] models) { + final List> modelClasses = new ArrayList>(); + final ClassLoader classLoader = mContext.getClass().getClassLoader(); + for (String model : models) { + try { + Class modelClass = Class.forName(model.trim(), false, classLoader); + if (ReflectionUtils.isModel(modelClass)) { + modelClasses.add(modelClass); + } + } + catch (ClassNotFoundException e) { + Log.e("Couldn't create class.", e); + } + } + + return modelClasses; + } + + private List> loadSerializerList(String[] serializers) { + final List> typeSerializers = new ArrayList>(); + final ClassLoader classLoader = mContext.getClass().getClassLoader(); + for (String serializer : serializers) { + try { + Class serializerClass = Class.forName(serializer.trim(), false, classLoader); + if (ReflectionUtils.isTypeSerializer(serializerClass)) { + typeSerializers.add(serializerClass); + } + } + catch (ClassNotFoundException e) { + Log.e("Couldn't create class.", e); + } + } + + return typeSerializers; + } + + } +} diff --git a/app/src/main/java/com/activeandroid/DatabaseHelper.java b/app/src/main/java/com/activeandroid/DatabaseHelper.java new file mode 100644 index 000000000..7158c5bb6 --- /dev/null +++ b/app/src/main/java/com/activeandroid/DatabaseHelper.java @@ -0,0 +1,257 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; + +import com.activeandroid.util.IOUtils; +import com.activeandroid.util.Log; +import com.activeandroid.util.NaturalOrderComparator; +import com.activeandroid.util.SQLiteUtils; +import com.activeandroid.util.SqlParser; + +public final class DatabaseHelper extends SQLiteOpenHelper { + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC CONSTANTS + ////////////////////////////////////////////////////////////////////////////////////// + + public final static String MIGRATION_PATH = "migrations"; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE FIELDS + ////////////////////////////////////////////////////////////////////////////////////// + + private final String mSqlParser; + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + public DatabaseHelper(Configuration configuration) { + super(configuration.getContext(), configuration.getDatabaseName(), null, configuration.getDatabaseVersion()); + copyAttachedDatabase(configuration.getContext(), configuration.getDatabaseName()); + mSqlParser = configuration.getSqlParser(); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDEN METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onOpen(SQLiteDatabase db) { + executePragmas(db); + }; + + @Override + public void onCreate(SQLiteDatabase db) { + executePragmas(db); + executeCreate(db); + executeMigrations(db, -1, db.getVersion()); + executeCreateIndex(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + executePragmas(db); + executeCreate(db); + executeMigrations(db, oldVersion, newVersion); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public void copyAttachedDatabase(Context context, String databaseName) { + final File dbPath = context.getDatabasePath(databaseName); + + // If the database already exists, return + if (dbPath.exists()) { + return; + } + + // Make sure we have a path to the file + dbPath.getParentFile().mkdirs(); + + // Try to copy database file + try { + final InputStream inputStream = context.getAssets().open(databaseName); + final OutputStream output = new FileOutputStream(dbPath); + + byte[] buffer = new byte[8192]; + int length; + + while ((length = inputStream.read(buffer, 0, 8192)) > 0) { + output.write(buffer, 0, length); + } + + output.flush(); + output.close(); + inputStream.close(); + } + catch (IOException e) { + Log.e("Failed to open file", e); + } + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + private void executePragmas(SQLiteDatabase db) { + if (SQLiteUtils.FOREIGN_KEYS_SUPPORTED) { + db.execSQL("PRAGMA foreign_keys=ON;"); + Log.i("Foreign Keys supported. Enabling foreign key features."); + } + } + + private void executeCreateIndex(SQLiteDatabase db) { + db.beginTransaction(); + try { + for (TableInfo tableInfo : Cache.getTableInfos()) { + String[] definitions = SQLiteUtils.createIndexDefinition(tableInfo); + + for (String definition : definitions) { + db.execSQL(definition); + } + } + db.setTransactionSuccessful(); + } + finally { + db.endTransaction(); + } + } + + private void executeCreate(SQLiteDatabase db) { + db.beginTransaction(); + try { + for (TableInfo tableInfo : Cache.getTableInfos()) { + db.execSQL(SQLiteUtils.createTableDefinition(tableInfo)); + } + db.setTransactionSuccessful(); + } + finally { + db.endTransaction(); + } + } + + private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVersion) { + boolean migrationExecuted = false; + try { + final List files = Arrays.asList(Cache.getContext().getAssets().list(MIGRATION_PATH)); + Collections.sort(files, new NaturalOrderComparator()); + + db.beginTransaction(); + try { + for (String file : files) { + try { + final int version = Integer.valueOf(file.replace(".sql", "")); + + if (version > oldVersion && version <= newVersion) { + executeSqlScript(db, file); + migrationExecuted = true; + + Log.i(file + " executed succesfully."); + } + } + catch (NumberFormatException e) { + Log.w("Skipping invalidly named file: " + file, e); + } + } + db.setTransactionSuccessful(); + } + finally { + db.endTransaction(); + } + } + catch (IOException e) { + Log.e("Failed to execute migrations.", e); + } + + return migrationExecuted; + } + + private void executeSqlScript(SQLiteDatabase db, String file) { + + InputStream stream = null; + + try { + stream = Cache.getContext().getAssets().open(MIGRATION_PATH + "/" + file); + + if (Configuration.SQL_PARSER_DELIMITED.equalsIgnoreCase(mSqlParser)) { + executeDelimitedSqlScript(db, stream); + + } else { + executeLegacySqlScript(db, stream); + + } + + } catch (IOException e) { + Log.e("Failed to execute " + file, e); + + } finally { + IOUtils.closeQuietly(stream); + + } + } + + private void executeDelimitedSqlScript(SQLiteDatabase db, InputStream stream) throws IOException { + + List commands = SqlParser.parse(stream); + + for(String command : commands) { + db.execSQL(command); + } + } + + private void executeLegacySqlScript(SQLiteDatabase db, InputStream stream) throws IOException { + + InputStreamReader reader = null; + BufferedReader buffer = null; + + try { + reader = new InputStreamReader(stream); + buffer = new BufferedReader(reader); + String line = null; + + while ((line = buffer.readLine()) != null) { + line = line.replace(";", "").trim(); + if (!TextUtils.isEmpty(line)) { + db.execSQL(line); + } + } + + } finally { + IOUtils.closeQuietly(buffer); + IOUtils.closeQuietly(reader); + + } + } +} diff --git a/app/src/main/java/com/activeandroid/Model.java b/app/src/main/java/com/activeandroid/Model.java new file mode 100644 index 000000000..4d22d9c89 --- /dev/null +++ b/app/src/main/java/com/activeandroid/Model.java @@ -0,0 +1,314 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.activeandroid.query.Delete; +import com.activeandroid.query.Select; +import com.activeandroid.serializer.TypeSerializer; +import com.activeandroid.util.Log; +import com.activeandroid.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings("unchecked") +public abstract class Model { + + /** Prime number used for hashcode() implementation. */ + private static final int HASH_PRIME = 739; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private Long mId = null; + + private final TableInfo mTableInfo; + private final String idName; + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + public Model() { + mTableInfo = Cache.getTableInfo(getClass()); + idName = mTableInfo.getIdName(); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public final Long getId() { + return mId; + } + + public final void delete() { + Cache.openDatabase().delete(mTableInfo.getTableName(), idName+"=?", new String[] { getId().toString() }); + Cache.removeEntity(this); + } + + public final Long save() { + final SQLiteDatabase db = Cache.openDatabase(); + final ContentValues values = new ContentValues(); + + for (Field field : mTableInfo.getFields()) { + final String fieldName = mTableInfo.getColumnName(field); + Class fieldType = field.getType(); + + field.setAccessible(true); + + try { + Object value = field.get(this); + + if (value != null) { + final TypeSerializer typeSerializer = Cache.getParserForType(fieldType); + if (typeSerializer != null) { + // serialize data + value = typeSerializer.serialize(value); + // set new object type + if (value != null) { + fieldType = value.getClass(); + // check that the serializer returned what it promised + if (!fieldType.equals(typeSerializer.getSerializedType())) { + Log.w(String.format("TypeSerializer returned wrong type: expected a %s but got a %s", + typeSerializer.getSerializedType(), fieldType)); + } + } + } + } + + // TODO: Find a smarter way to do this? This if block is necessary because we + // can't know the type until runtime. + if (value == null) { + values.putNull(fieldName); + } + else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) { + values.put(fieldName, (Byte) value); + } + else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) { + values.put(fieldName, (Short) value); + } + else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) { + values.put(fieldName, (Integer) value); + } + else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) { + values.put(fieldName, (Long) value); + } + else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) { + values.put(fieldName, (Float) value); + } + else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) { + values.put(fieldName, (Double) value); + } + else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) { + values.put(fieldName, (Boolean) value); + } + else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) { + values.put(fieldName, value.toString()); + } + else if (fieldType.equals(String.class)) { + values.put(fieldName, value.toString()); + } + else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) { + values.put(fieldName, (byte[]) value); + } + else if (ReflectionUtils.isModel(fieldType)) { + values.put(fieldName, ((Model) value).getId()); + } + else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) { + values.put(fieldName, ((Enum) value).name()); + } + } + catch (IllegalArgumentException e) { + Log.e(e.getClass().getName(), e); + } + catch (IllegalAccessException e) { + Log.e(e.getClass().getName(), e); + } + } + + if (mId == null) { + mId = db.insert(mTableInfo.getTableName(), null, values); + } + else { + db.update(mTableInfo.getTableName(), values, idName+"=" + mId, null); + } + + return mId; + } + + // Convenience methods + + public static void delete(Class type, long id) { + TableInfo tableInfo = Cache.getTableInfo(type); + new Delete().from(type).where(tableInfo.getIdName()+"=?", id).execute(); + } + + public static T load(Class type, long id) { + TableInfo tableInfo = Cache.getTableInfo(type); + return (T) new Select().from(type).where(tableInfo.getIdName()+"=?", id).executeSingle(); + } + + // Model population + + public final void loadFromCursor(Cursor cursor) { + /** + * Obtain the columns ordered to fix issue #106 (https://github.com/pardom/ActiveAndroid/issues/106) + * when the cursor have multiple columns with same name obtained from join tables. + */ + List columnsOrdered = new ArrayList(Arrays.asList(cursor.getColumnNames())); + for (Field field : mTableInfo.getFields()) { + final String fieldName = mTableInfo.getColumnName(field); + Class fieldType = field.getType(); + final int columnIndex = columnsOrdered.indexOf(fieldName); + + if (columnIndex < 0) { + continue; + } + + field.setAccessible(true); + + try { + boolean columnIsNull = cursor.isNull(columnIndex); + TypeSerializer typeSerializer = Cache.getParserForType(fieldType); + Object value = null; + + if (typeSerializer != null) { + fieldType = typeSerializer.getSerializedType(); + } + + // TODO: Find a smarter way to do this? This if block is necessary because we + // can't know the type until runtime. + if (columnIsNull) { + field = null; + } + else if (fieldType.equals(Byte.class) || fieldType.equals(byte.class)) { + value = cursor.getInt(columnIndex); + } + else if (fieldType.equals(Short.class) || fieldType.equals(short.class)) { + value = cursor.getInt(columnIndex); + } + else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) { + value = cursor.getInt(columnIndex); + } + else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) { + value = cursor.getLong(columnIndex); + } + else if (fieldType.equals(Float.class) || fieldType.equals(float.class)) { + value = cursor.getFloat(columnIndex); + } + else if (fieldType.equals(Double.class) || fieldType.equals(double.class)) { + value = cursor.getDouble(columnIndex); + } + else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) { + value = cursor.getInt(columnIndex) != 0; + } + else if (fieldType.equals(Character.class) || fieldType.equals(char.class)) { + value = cursor.getString(columnIndex).charAt(0); + } + else if (fieldType.equals(String.class)) { + value = cursor.getString(columnIndex); + } + else if (fieldType.equals(Byte[].class) || fieldType.equals(byte[].class)) { + value = cursor.getBlob(columnIndex); + } + else if (ReflectionUtils.isModel(fieldType)) { + final long entityId = cursor.getLong(columnIndex); + final Class entityType = (Class) fieldType; + + Model entity = Cache.getEntity(entityType, entityId); + if (entity == null) { + entity = new Select().from(entityType).where(idName+"=?", entityId).executeSingle(); + } + + value = entity; + } + else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) { + @SuppressWarnings("rawtypes") + final Class enumType = (Class) fieldType; + value = Enum.valueOf(enumType, cursor.getString(columnIndex)); + } + + // Use a deserializer if one is available + if (typeSerializer != null && !columnIsNull) { + value = typeSerializer.deserialize(value); + } + + // Set the field value + if (value != null) { + field.set(this, value); + } + } + catch (IllegalArgumentException e) { + Log.e(e.getClass().getName(), e); + } + catch (IllegalAccessException e) { + Log.e(e.getClass().getName(), e); + } + catch (SecurityException e) { + Log.e(e.getClass().getName(), e); + } + } + + if (mId != null) { + Cache.addEntity(this); + } + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PROTECTED METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + protected final List getMany(Class type, String foreignKey) { + return new Select().from(type).where(Cache.getTableName(type) + "." + foreignKey + "=?", getId()).execute(); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDEN METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + @Override + public String toString() { + return mTableInfo.getTableName() + "@" + getId(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Model && this.mId != null) { + final Model other = (Model) obj; + + return this.mId.equals(other.mId) + && (this.mTableInfo.getTableName().equals(other.mTableInfo.getTableName())); + } else { + return this == obj; + } + } + + @Override + public int hashCode() { + int hash = HASH_PRIME; + hash += HASH_PRIME * (mId == null ? super.hashCode() : mId.hashCode()); //if id is null, use Object.hashCode() + hash += HASH_PRIME * mTableInfo.getTableName().hashCode(); + return hash; //To change body of generated methods, choose Tools | Templates. + } +} diff --git a/app/src/main/java/com/activeandroid/ModelInfo.java b/app/src/main/java/com/activeandroid/ModelInfo.java new file mode 100644 index 000000000..09e79117c --- /dev/null +++ b/app/src/main/java/com/activeandroid/ModelInfo.java @@ -0,0 +1,209 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.content.Context; + +import com.activeandroid.serializer.CalendarSerializer; +import com.activeandroid.serializer.SqlDateSerializer; +import com.activeandroid.serializer.TypeSerializer; +import com.activeandroid.serializer.UtilDateSerializer; +import com.activeandroid.serializer.FileSerializer; +import com.activeandroid.util.Log; +import com.activeandroid.util.ReflectionUtils; +import dalvik.system.DexFile; + +final class ModelInfo { + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + private Map, TableInfo> mTableInfos = new HashMap, TableInfo>(); + private Map, TypeSerializer> mTypeSerializers = new HashMap, TypeSerializer>() { + { + put(Calendar.class, new CalendarSerializer()); + put(java.sql.Date.class, new SqlDateSerializer()); + put(java.util.Date.class, new UtilDateSerializer()); + put(java.io.File.class, new FileSerializer()); + } + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + public ModelInfo(Configuration configuration) { + if (!loadModelFromMetaData(configuration)) { + try { + scanForModel(configuration.getContext()); + } + catch (IOException e) { + Log.e("Couldn't open source path.", e); + } + } + + Log.i("ModelInfo loaded."); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public Collection getTableInfos() { + return mTableInfos.values(); + } + + public TableInfo getTableInfo(Class type) { + return mTableInfos.get(type); + } + + public TypeSerializer getTypeSerializer(Class type) { + return mTypeSerializers.get(type); + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + private boolean loadModelFromMetaData(Configuration configuration) { + if (!configuration.isValid()) { + return false; + } + + final List> models = configuration.getModelClasses(); + if (models != null) { + for (Class model : models) { + mTableInfos.put(model, new TableInfo(model)); + } + } + + final List> typeSerializers = configuration.getTypeSerializers(); + if (typeSerializers != null) { + for (Class typeSerializer : typeSerializers) { + try { + TypeSerializer instance = typeSerializer.newInstance(); + mTypeSerializers.put(instance.getDeserializedType(), instance); + } + catch (InstantiationException e) { + Log.e("Couldn't instantiate TypeSerializer.", e); + } + catch (IllegalAccessException e) { + Log.e("IllegalAccessException", e); + } + } + } + + return true; + } + + private void scanForModel(Context context) throws IOException { + String packageName = context.getPackageName(); + String sourcePath = context.getApplicationInfo().sourceDir; + List paths = new ArrayList(); + + if (sourcePath != null && !(new File(sourcePath).isDirectory())) { + DexFile dexfile = new DexFile(sourcePath); + Enumeration entries = dexfile.entries(); + + while (entries.hasMoreElements()) { + paths.add(entries.nextElement()); + } + } + // Robolectric fallback + else { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Enumeration resources = classLoader.getResources(""); + + while (resources.hasMoreElements()) { + String path = resources.nextElement().getFile(); + if (path.contains("bin") || path.contains("classes")) { + paths.add(path); + } + } + } + + for (String path : paths) { + File file = new File(path); + scanForModelClasses(file, packageName, context.getClassLoader()); + } + } + + private void scanForModelClasses(File path, String packageName, ClassLoader classLoader) { + if (path.isDirectory()) { + for (File file : path.listFiles()) { + scanForModelClasses(file, packageName, classLoader); + } + } + else { + String className = path.getName(); + + // Robolectric fallback + if (!path.getPath().equals(className)) { + className = path.getPath(); + + if (className.endsWith(".class")) { + className = className.substring(0, className.length() - 6); + } + else { + return; + } + + className = className.replace(System.getProperty("file.separator"), "."); + + int packageNameIndex = className.lastIndexOf(packageName); + if (packageNameIndex < 0) { + return; + } + + className = className.substring(packageNameIndex); + } + + try { + Class discoveredClass = Class.forName(className, false, classLoader); + if (ReflectionUtils.isModel(discoveredClass)) { + @SuppressWarnings("unchecked") + Class modelClass = (Class) discoveredClass; + mTableInfos.put(modelClass, new TableInfo(modelClass)); + } + else if (ReflectionUtils.isTypeSerializer(discoveredClass)) { + TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance(); + mTypeSerializers.put(instance.getDeserializedType(), instance); + } + } + catch (ClassNotFoundException e) { + Log.e("Couldn't create class.", e); + } + catch (InstantiationException e) { + Log.e("Couldn't instantiate TypeSerializer.", e); + } + catch (IllegalAccessException e) { + Log.e("IllegalAccessException", e); + } + } + } +} diff --git a/app/src/main/java/com/activeandroid/TableInfo.java b/app/src/main/java/com/activeandroid/TableInfo.java new file mode 100644 index 000000000..32d1ecb3f --- /dev/null +++ b/app/src/main/java/com/activeandroid/TableInfo.java @@ -0,0 +1,124 @@ +package com.activeandroid; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import android.text.TextUtils; +import android.util.Log; + +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; +import com.activeandroid.util.ReflectionUtils; + +public final class TableInfo { + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private Class mType; + private String mTableName; + private String mIdName = Table.DEFAULT_ID_NAME; + + private Map mColumnNames = new LinkedHashMap(); + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + public TableInfo(Class type) { + mType = type; + + final Table tableAnnotation = type.getAnnotation(Table.class); + + if (tableAnnotation != null) { + mTableName = tableAnnotation.name(); + mIdName = tableAnnotation.id(); + } + else { + mTableName = type.getSimpleName(); + } + + // Manually add the id column since it is not declared like the other columns. + Field idField = getIdField(type); + mColumnNames.put(idField, mIdName); + + List fields = new LinkedList(ReflectionUtils.getDeclaredColumnFields(type)); + Collections.reverse(fields); + + for (Field field : fields) { + if (field.isAnnotationPresent(Column.class)) { + final Column columnAnnotation = field.getAnnotation(Column.class); + String columnName = columnAnnotation.name(); + if (TextUtils.isEmpty(columnName)) { + columnName = field.getName(); + } + + mColumnNames.put(field, columnName); + } + } + + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public Class getType() { + return mType; + } + + public String getTableName() { + return mTableName; + } + + public String getIdName() { + return mIdName; + } + + public Collection getFields() { + return mColumnNames.keySet(); + } + + public String getColumnName(Field field) { + return mColumnNames.get(field); + } + + + private Field getIdField(Class type) { + if (type.equals(Model.class)) { + try { + return type.getDeclaredField("mId"); + } + catch (NoSuchFieldException e) { + Log.e("Impossible!", e.toString()); + } + } + else if (type.getSuperclass() != null) { + return getIdField(type.getSuperclass()); + } + + return null; + } + +} diff --git a/app/src/main/java/com/activeandroid/annotation/Column.java b/app/src/main/java/com/activeandroid/annotation/Column.java new file mode 100644 index 000000000..56bbcde85 --- /dev/null +++ b/app/src/main/java/com/activeandroid/annotation/Column.java @@ -0,0 +1,110 @@ +package com.activeandroid.annotation; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + public enum ConflictAction { + ROLLBACK, ABORT, FAIL, IGNORE, REPLACE + } + + public enum ForeignKeyAction { + SET_NULL, SET_DEFAULT, CASCADE, RESTRICT, NO_ACTION + } + + public String name() default ""; + + public int length() default -1; + + public boolean notNull() default false; + + public ConflictAction onNullConflict() default ConflictAction.FAIL; + + public ForeignKeyAction onDelete() default ForeignKeyAction.NO_ACTION; + + public ForeignKeyAction onUpdate() default ForeignKeyAction.NO_ACTION; + + public boolean unique() default false; + + public ConflictAction onUniqueConflict() default ConflictAction.FAIL; + + /* + * If set uniqueGroups = {"group_name"}, we will create a table constraint with group. + * + * Example: + * + * @Table(name = "table_name") + * public class Table extends Model { + * @Column(name = "member1", uniqueGroups = {"group1"}, onUniqueConflicts = {ConflictAction.FAIL}) + * public String member1; + * + * @Column(name = "member2", uniqueGroups = {"group1", "group2"}, onUniqueConflicts = {ConflictAction.FAIL, ConflictAction.IGNORE}) + * public String member2; + * + * @Column(name = "member3", uniqueGroups = {"group2"}, onUniqueConflicts = {ConflictAction.IGNORE}) + * public String member3; + * } + * + * CREATE TABLE table_name (..., UNIQUE (member1, member2) ON CONFLICT FAIL, UNIQUE (member2, member3) ON CONFLICT IGNORE) + */ + public String[] uniqueGroups() default {}; + + public ConflictAction[] onUniqueConflicts() default {}; + + /* + * If set index = true, we will create a index with single column. + * + * Example: + * + * @Table(name = "table_name") + * public class Table extends Model { + * @Column(name = "member", index = true) + * public String member; + * } + * + * Execute CREATE INDEX index_table_name_member on table_name(member) + */ + public boolean index() default false; + + /* + * If set indexGroups = {"group_name"}, we will create a index with group. + * + * Example: + * + * @Table(name = "table_name") + * public class Table extends Model { + * @Column(name = "member1", indexGroups = {"group1"}) + * public String member1; + * + * @Column(name = "member2", indexGroups = {"group1", "group2"}) + * public String member2; + * + * @Column(name = "member3", indexGroups = {"group2"}) + * public String member3; + * } + * + * Execute CREATE INDEX index_table_name_group1 on table_name(member1, member2) + * Execute CREATE INDEX index_table_name_group2 on table_name(member2, member3) + */ + public String[] indexGroups() default {}; +} diff --git a/app/src/main/java/com/activeandroid/annotation/Table.java b/app/src/main/java/com/activeandroid/annotation/Table.java new file mode 100644 index 000000000..541dfbe92 --- /dev/null +++ b/app/src/main/java/com/activeandroid/annotation/Table.java @@ -0,0 +1,31 @@ +package com.activeandroid.annotation; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Table { + + public static final String DEFAULT_ID_NAME = "Id"; + public String name(); + public String id() default DEFAULT_ID_NAME; +} diff --git a/app/src/main/java/com/activeandroid/query/Delete.java b/app/src/main/java/com/activeandroid/query/Delete.java new file mode 100644 index 000000000..6d19dcedc --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/Delete.java @@ -0,0 +1,33 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.activeandroid.Model; + +public final class Delete implements Sqlable { + public Delete() { + } + + public From from(Class table) { + return new From(table, this); + } + + @Override + public String toSql() { + return "DELETE "; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/query/From.java b/app/src/main/java/com/activeandroid/query/From.java new file mode 100644 index 000000000..825e36db7 --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/From.java @@ -0,0 +1,344 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.text.TextUtils; + +import com.activeandroid.Cache; +import com.activeandroid.Model; +import com.activeandroid.query.Join.JoinType; +import com.activeandroid.util.Log; +import com.activeandroid.util.SQLiteUtils; + +import java.util.ArrayList; +import java.util.List; + +public final class From implements Sqlable { + private Sqlable mQueryBase; + + private Class mType; + private String mAlias; + private List mJoins; + private final StringBuilder mWhere = new StringBuilder(); + private String mGroupBy; + private String mHaving; + private String mOrderBy; + private String mLimit; + private String mOffset; + + private List mArguments; + + public From(Class table, Sqlable queryBase) { + mType = table; + mJoins = new ArrayList(); + mQueryBase = queryBase; + + mJoins = new ArrayList(); + mArguments = new ArrayList(); + } + + public From as(String alias) { + mAlias = alias; + return this; + } + + public Join join(Class table) { + Join join = new Join(this, table, null); + mJoins.add(join); + return join; + } + + public Join leftJoin(Class table) { + Join join = new Join(this, table, JoinType.LEFT); + mJoins.add(join); + return join; + } + + public Join outerJoin(Class table) { + Join join = new Join(this, table, JoinType.OUTER); + mJoins.add(join); + return join; + } + + public Join innerJoin(Class table) { + Join join = new Join(this, table, JoinType.INNER); + mJoins.add(join); + return join; + } + + public Join crossJoin(Class table) { + Join join = new Join(this, table, JoinType.CROSS); + mJoins.add(join); + return join; + } + + public From where(String clause) { + // Chain conditions if a previous condition exists. + if (mWhere.length() > 0) { + mWhere.append(" AND "); + } + mWhere.append(clause); + return this; + } + + public From where(String clause, Object... args) { + where(clause).addArguments(args); + return this; + } + + public From and(String clause) { + return where(clause); + } + + public From and(String clause, Object... args) { + return where(clause, args); + } + + public From or(String clause) { + if (mWhere.length() > 0) { + mWhere.append(" OR "); + } + mWhere.append(clause); + return this; + } + + public From or(String clause, Object... args) { + or(clause).addArguments(args); + return this; + } + + public From groupBy(String groupBy) { + mGroupBy = groupBy; + return this; + } + + public From having(String having) { + mHaving = having; + return this; + } + + public From orderBy(String orderBy) { + mOrderBy = orderBy; + return this; + } + + public From limit(int limit) { + return limit(String.valueOf(limit)); + } + + public From limit(String limit) { + mLimit = limit; + return this; + } + + public From offset(int offset) { + return offset(String.valueOf(offset)); + } + + public From offset(String offset) { + mOffset = offset; + return this; + } + + void addArguments(Object[] args) { + for(Object arg : args) { + if (arg.getClass() == boolean.class || arg.getClass() == Boolean.class) { + arg = (arg.equals(true) ? 1 : 0); + } + mArguments.add(arg); + } + } + + private void addFrom(final StringBuilder sql) { + sql.append("FROM "); + sql.append(Cache.getTableName(mType)).append(" "); + + if (mAlias != null) { + sql.append("AS "); + sql.append(mAlias); + sql.append(" "); + } + } + + private void addJoins(final StringBuilder sql) { + for (final Join join : mJoins) { + sql.append(join.toSql()); + } + } + + private void addWhere(final StringBuilder sql) { + if (mWhere.length() > 0) { + sql.append("WHERE "); + sql.append(mWhere); + sql.append(" "); + } + } + + private void addGroupBy(final StringBuilder sql) { + if (mGroupBy != null) { + sql.append("GROUP BY "); + sql.append(mGroupBy); + sql.append(" "); + } + } + + private void addHaving(final StringBuilder sql) { + if (mHaving != null) { + sql.append("HAVING "); + sql.append(mHaving); + sql.append(" "); + } + } + + private void addOrderBy(final StringBuilder sql) { + if (mOrderBy != null) { + sql.append("ORDER BY "); + sql.append(mOrderBy); + sql.append(" "); + } + } + + private void addLimit(final StringBuilder sql) { + if (mLimit != null) { + sql.append("LIMIT "); + sql.append(mLimit); + sql.append(" "); + } + } + + private void addOffset(final StringBuilder sql) { + if (mOffset != null) { + sql.append("OFFSET "); + sql.append(mOffset); + sql.append(" "); + } + } + + private String sqlString(final StringBuilder sql) { + + final String sqlString = sql.toString().trim(); + + // Don't waste time building the string + // unless we're going to log it. + if (Log.isEnabled()) { + Log.v(sqlString + " " + TextUtils.join(",", getArguments())); + } + + return sqlString; + } + + @Override + public String toSql() { + final StringBuilder sql = new StringBuilder(); + sql.append(mQueryBase.toSql()); + + addFrom(sql); + addJoins(sql); + addWhere(sql); + addGroupBy(sql); + addHaving(sql); + addOrderBy(sql); + addLimit(sql); + addOffset(sql); + + return sqlString(sql); + } + + public String toExistsSql() { + + final StringBuilder sql = new StringBuilder(); + sql.append("SELECT EXISTS(SELECT 1 "); + + addFrom(sql); + addJoins(sql); + addWhere(sql); + addGroupBy(sql); + addHaving(sql); + addLimit(sql); + addOffset(sql); + + sql.append(")"); + + return sqlString(sql); + } + + public String toCountSql() { + + final StringBuilder sql = new StringBuilder(); + sql.append("SELECT COUNT(*) "); + + addFrom(sql); + addJoins(sql); + addWhere(sql); + addGroupBy(sql); + addHaving(sql); + addLimit(sql); + addOffset(sql); + + return sqlString(sql); + } + + public List execute() { + if (mQueryBase instanceof Select) { + return SQLiteUtils.rawQuery(mType, toSql(), getArguments()); + + } else { + SQLiteUtils.execSql(toSql(), getArguments()); + return null; + + } + } + + public T executeSingle() { + if (mQueryBase instanceof Select) { + limit(1); + return (T) SQLiteUtils.rawQuerySingle(mType, toSql(), getArguments()); + + } else { + limit(1); + SQLiteUtils.rawQuerySingle(mType, toSql(), getArguments()).delete(); + return null; + + } + } + + /** + * Gets a value indicating whether the query returns any rows. + * @return true if the query returns at least one row; otherwise, false. + */ + public boolean exists() { + return SQLiteUtils.intQuery(toExistsSql(), getArguments()) != 0; + } + + /** + * Gets the number of rows returned by the query. + */ + public int count() { + return SQLiteUtils.intQuery(toCountSql(), getArguments()); + } + + public String[] getArguments() { + final int size = mArguments.size(); + final String[] args = new String[size]; + + for (int i = 0; i < size; i++) { + args[i] = mArguments.get(i).toString(); + } + + return args; + } +} diff --git a/app/src/main/java/com/activeandroid/query/Join.java b/app/src/main/java/com/activeandroid/query/Join.java new file mode 100644 index 000000000..13cdba3be --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/Join.java @@ -0,0 +1,94 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.text.TextUtils; + +import com.activeandroid.Cache; +import com.activeandroid.Model; + +public final class Join implements Sqlable { + static enum JoinType { + LEFT, OUTER, INNER, CROSS + } + + private From mFrom; + private Class mType; + private String mAlias; + private JoinType mJoinType; + private String mOn; + private String[] mUsing; + + Join(From from, Class table, JoinType joinType) { + mFrom = from; + mType = table; + mJoinType = joinType; + } + + public Join as(String alias) { + mAlias = alias; + return this; + } + + public From on(String on) { + mOn = on; + return mFrom; + } + + public From on(String on, Object... args) { + mOn = on; + mFrom.addArguments(args); + return mFrom; + } + + public From using(String... columns) { + mUsing = columns; + return mFrom; + } + + @Override + public String toSql() { + StringBuilder sql = new StringBuilder(); + + if (mJoinType != null) { + sql.append(mJoinType.toString()).append(" "); + } + + sql.append("JOIN "); + sql.append(Cache.getTableName(mType)); + sql.append(" "); + + if (mAlias != null) { + sql.append("AS "); + sql.append(mAlias); + sql.append(" "); + } + + if (mOn != null) { + sql.append("ON "); + sql.append(mOn); + sql.append(" "); + } + else if (mUsing != null) { + sql.append("USING ("); + sql.append(TextUtils.join(", ", mUsing)); + sql.append(") "); + } + + return sql.toString(); + } +} diff --git a/app/src/main/java/com/activeandroid/query/Select.java b/app/src/main/java/com/activeandroid/query/Select.java new file mode 100644 index 000000000..1d4c64885 --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/Select.java @@ -0,0 +1,93 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.text.TextUtils; + +import com.activeandroid.Model; + +public final class Select implements Sqlable { + private String[] mColumns; + private boolean mDistinct = false; + private boolean mAll = false; + + public Select() { + } + + public Select(String... columns) { + mColumns = columns; + } + + public Select(Column... columns) { + final int size = columns.length; + mColumns = new String[size]; + for (int i = 0; i < size; i++) { + mColumns[i] = columns[i].name + " AS " + columns[i].alias; + } + } + + public Select distinct() { + mDistinct = true; + mAll = false; + + return this; + } + + public Select all() { + mDistinct = false; + mAll = true; + + return this; + } + + public From from(Class table) { + return new From(table, this); + } + + public static class Column { + String name; + String alias; + + public Column(String name, String alias) { + this.name = name; + this.alias = alias; + } + } + + @Override + public String toSql() { + StringBuilder sql = new StringBuilder(); + + sql.append("SELECT "); + + if (mDistinct) { + sql.append("DISTINCT "); + } + else if (mAll) { + sql.append("ALL "); + } + + if (mColumns != null && mColumns.length > 0) { + sql.append(TextUtils.join(", ", mColumns) + " "); + } + else { + sql.append("* "); + } + + return sql.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/query/Set.java b/app/src/main/java/com/activeandroid/query/Set.java new file mode 100644 index 000000000..183d99f0f --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/Set.java @@ -0,0 +1,103 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.activeandroid.util.SQLiteUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class Set implements Sqlable { + private Update mUpdate; + + private String mSet; + private String mWhere; + + private List mSetArguments; + private List mWhereArguments; + + public Set(Update queryBase, String set) { + mUpdate = queryBase; + mSet = set; + + mSetArguments = new ArrayList(); + mWhereArguments = new ArrayList(); + } + + public Set(Update queryBase, String set, Object... args) { + mUpdate = queryBase; + mSet = set; + + mSetArguments = new ArrayList(); + mWhereArguments = new ArrayList(); + + mSetArguments.addAll(Arrays.asList(args)); + } + + public Set where(String where) { + mWhere = where; + mWhereArguments.clear(); + + return this; + } + + public Set where(String where, Object... args) { + mWhere = where; + mWhereArguments.clear(); + mWhereArguments.addAll(Arrays.asList(args)); + + return this; + } + + @Override + public String toSql() { + StringBuilder sql = new StringBuilder(); + sql.append(mUpdate.toSql()); + sql.append("SET "); + sql.append(mSet); + sql.append(" "); + + if (mWhere != null) { + sql.append("WHERE "); + sql.append(mWhere); + sql.append(" "); + } + + return sql.toString(); + } + + public void execute() { + SQLiteUtils.execSql(toSql(), getArguments()); + } + + public String[] getArguments() { + final int setSize = mSetArguments.size(); + final int whereSize = mWhereArguments.size(); + final String[] args = new String[setSize + whereSize]; + + for (int i = 0; i < setSize; i++) { + args[i] = mSetArguments.get(i).toString(); + } + + for (int i = 0; i < whereSize; i++) { + args[i + setSize] = mWhereArguments.get(i).toString(); + } + + return args; + } +} diff --git a/app/src/main/java/com/activeandroid/query/Sqlable.java b/app/src/main/java/com/activeandroid/query/Sqlable.java new file mode 100644 index 000000000..2c3f5d437 --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/Sqlable.java @@ -0,0 +1,21 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public interface Sqlable { + public String toSql(); +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/query/Update.java b/app/src/main/java/com/activeandroid/query/Update.java new file mode 100644 index 000000000..a69d2d8e0 --- /dev/null +++ b/app/src/main/java/com/activeandroid/query/Update.java @@ -0,0 +1,50 @@ +package com.activeandroid.query; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.activeandroid.Cache; +import com.activeandroid.Model; + +public final class Update implements Sqlable { + private Class mType; + + public Update(Class table) { + mType = table; + } + + public Set set(String set) { + return new Set(this, set); + } + + public Set set(String set, Object... args) { + return new Set(this, set, args); + } + + Class getType() { + return mType; + } + + @Override + public String toSql() { + StringBuilder sql = new StringBuilder(); + sql.append("UPDATE "); + sql.append(Cache.getTableName(mType)); + sql.append(" "); + + return sql.toString(); + } +} diff --git a/app/src/main/java/com/activeandroid/serializer/BigDecimalSerializer.java b/app/src/main/java/com/activeandroid/serializer/BigDecimalSerializer.java new file mode 100644 index 000000000..333f900f0 --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/BigDecimalSerializer.java @@ -0,0 +1,29 @@ +package com.activeandroid.serializer; + +import java.math.BigDecimal; + +public final class BigDecimalSerializer extends TypeSerializer { + public Class getDeserializedType() { + return BigDecimal.class; + } + + public Class getSerializedType() { + return String.class; + } + + public String serialize(Object data) { + if (data == null) { + return null; + } + + return ((BigDecimal) data).toString(); + } + + public BigDecimal deserialize(Object data) { + if (data == null) { + return null; + } + + return new BigDecimal((String) data); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/serializer/CalendarSerializer.java b/app/src/main/java/com/activeandroid/serializer/CalendarSerializer.java new file mode 100644 index 000000000..55509bd08 --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/CalendarSerializer.java @@ -0,0 +1,40 @@ +package com.activeandroid.serializer; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Calendar; + +public final class CalendarSerializer extends TypeSerializer { + public Class getDeserializedType() { + return Calendar.class; + } + + public Class getSerializedType() { + return long.class; + } + + public Long serialize(Object data) { + return ((Calendar) data).getTimeInMillis(); + } + + public Calendar deserialize(Object data) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis((Long) data); + + return calendar; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/serializer/FileSerializer.java b/app/src/main/java/com/activeandroid/serializer/FileSerializer.java new file mode 100644 index 000000000..0aed072c0 --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/FileSerializer.java @@ -0,0 +1,46 @@ +package com.activeandroid.serializer; + +import java.io.File; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +public final class FileSerializer extends TypeSerializer { + public Class getDeserializedType() { + return File.class; + } + + public Class getSerializedType() { + return String.class; + } + + public String serialize(Object data) { + if (data == null) { + return null; + } + + return ((File) data).toString(); + } + + public File deserialize(Object data) { + if (data == null) { + return null; + } + + return new File((String) data); + } +} diff --git a/app/src/main/java/com/activeandroid/serializer/SqlDateSerializer.java b/app/src/main/java/com/activeandroid/serializer/SqlDateSerializer.java new file mode 100644 index 000000000..530d1249b --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/SqlDateSerializer.java @@ -0,0 +1,45 @@ +package com.activeandroid.serializer; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.sql.Date; + +public final class SqlDateSerializer extends TypeSerializer { + public Class getDeserializedType() { + return Date.class; + } + + public Class getSerializedType() { + return long.class; + } + + public Long serialize(Object data) { + if (data == null) { + return null; + } + + return ((Date) data).getTime(); + } + + public Date deserialize(Object data) { + if (data == null) { + return null; + } + + return new Date((Long) data); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/serializer/TypeSerializer.java b/app/src/main/java/com/activeandroid/serializer/TypeSerializer.java new file mode 100644 index 000000000..af0a21ded --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/TypeSerializer.java @@ -0,0 +1,27 @@ +package com.activeandroid.serializer; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public abstract class TypeSerializer { + public abstract Class getDeserializedType(); + + public abstract Class getSerializedType(); + + public abstract Object serialize(Object data); + + public abstract Object deserialize(Object data); +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/serializer/UUIDSerializer.java b/app/src/main/java/com/activeandroid/serializer/UUIDSerializer.java new file mode 100644 index 000000000..94ba37ff3 --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/UUIDSerializer.java @@ -0,0 +1,29 @@ +package com.activeandroid.serializer; + +import java.util.UUID; + +public final class UUIDSerializer extends TypeSerializer { + public Class getDeserializedType() { + return UUID.class; + } + + public Class getSerializedType() { + return String.class; + } + + public String serialize(Object data) { + if (data == null) { + return null; + } + + return ((UUID) data).toString(); + } + + public UUID deserialize(Object data) { + if (data == null) { + return null; + } + + return UUID.fromString((String)data); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/serializer/UtilDateSerializer.java b/app/src/main/java/com/activeandroid/serializer/UtilDateSerializer.java new file mode 100644 index 000000000..a82c7ef15 --- /dev/null +++ b/app/src/main/java/com/activeandroid/serializer/UtilDateSerializer.java @@ -0,0 +1,45 @@ +package com.activeandroid.serializer; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Date; + +public final class UtilDateSerializer extends TypeSerializer { + public Class getDeserializedType() { + return Date.class; + } + + public Class getSerializedType() { + return long.class; + } + + public Long serialize(Object data) { + if (data == null) { + return null; + } + + return ((Date) data).getTime(); + } + + public Date deserialize(Object data) { + if (data == null) { + return null; + } + + return new Date((Long) data); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/util/IOUtils.java b/app/src/main/java/com/activeandroid/util/IOUtils.java new file mode 100644 index 000000000..b3005f857 --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/IOUtils.java @@ -0,0 +1,71 @@ + +package com.activeandroid.util; + +/* + * Copyright (C) 2014 Markus Pfeiffer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.database.Cursor; + +import java.io.Closeable; +import java.io.IOException; + +import com.activeandroid.util.Log; + + +public class IOUtils { + + /** + *

+ * Unconditionally close a {@link Closeable}. + *

+ * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is + * typically used in finally blocks. + * @param closeable A {@link Closeable} to close. + */ + public static void closeQuietly(final Closeable closeable) { + + if (closeable == null) { + return; + } + + try { + closeable.close(); + } catch (final IOException e) { + Log.e("Couldn't close closeable.", e); + } + } + + /** + *

+ * Unconditionally close a {@link Cursor}. + *

+ * Equivalent to {@link Cursor#close()}, except any exceptions will be ignored. This is + * typically used in finally blocks. + * @param cursor A {@link Cursor} to close. + */ + public static void closeQuietly(final Cursor cursor) { + + if (cursor == null) { + return; + } + + try { + cursor.close(); + } catch (final Exception e) { + Log.e("Couldn't close cursor.", e); + } + } +} diff --git a/app/src/main/java/com/activeandroid/util/Log.java b/app/src/main/java/com/activeandroid/util/Log.java new file mode 100644 index 000000000..1c2a384d5 --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/Log.java @@ -0,0 +1,196 @@ +package com.activeandroid.util; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public final class Log { + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private static String sTag = "ActiveAndroid"; + private static boolean sEnabled = false; + + ////////////////////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS + ////////////////////////////////////////////////////////////////////////////////////// + + private Log() { + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public static boolean isEnabled() { + return sEnabled; + } + + public static void setEnabled(boolean enabled) { + sEnabled = enabled; + } + + public static boolean isLoggingEnabled() { + return sEnabled; + } + + public static int v(String msg) { + if (sEnabled) { + return android.util.Log.v(sTag, msg); + } + return 0; + } + + public static int v(String tag, String msg) { + if (sEnabled) { + return android.util.Log.v(tag, msg); + } + return 0; + } + + public static int v(String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.v(sTag, msg, tr); + } + return 0; + } + + public static int v(String tag, String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.v(tag, msg, tr); + } + return 0; + } + + public static int d(String msg) { + if (sEnabled) { + return android.util.Log.d(sTag, msg); + } + return 0; + } + + public static int d(String tag, String msg) { + if (sEnabled) { + return android.util.Log.d(tag, msg); + } + return 0; + } + + public static int d(String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.d(sTag, msg, tr); + } + return 0; + } + + public static int d(String tag, String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.d(tag, msg, tr); + } + return 0; + } + + public static int i(String msg) { + if (sEnabled) { + return android.util.Log.i(sTag, msg); + } + return 0; + } + + public static int i(String tag, String msg) { + if (sEnabled) { + return android.util.Log.i(tag, msg); + } + return 0; + } + + public static int i(String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.i(sTag, msg, tr); + } + return 0; + } + + public static int i(String tag, String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.i(tag, msg, tr); + } + return 0; + } + + public static int w(String msg) { + if (sEnabled) { + return android.util.Log.w(sTag, msg); + } + return 0; + } + + public static int w(String tag, String msg) { + if (sEnabled) { + return android.util.Log.w(tag, msg); + } + return 0; + } + + public static int w(String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.w(sTag, msg, tr); + } + return 0; + } + + public static int w(String tag, String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.w(tag, msg, tr); + } + return 0; + } + + public static int e(String msg) { + if (sEnabled) { + return android.util.Log.e(sTag, msg); + } + return 0; + } + + public static int e(String tag, String msg) { + if (sEnabled) { + return android.util.Log.e(tag, msg); + } + return 0; + } + + public static int e(String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.e(sTag, msg, tr); + } + return 0; + } + + public static int e(String tag, String msg, Throwable tr) { + if (sEnabled) { + return android.util.Log.e(tag, msg, tr); + } + return 0; + } + + public static int t(String msg, Object... args) { + if (sEnabled) { + return android.util.Log.v("test", String.format(msg, args)); + } + return 0; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/util/NaturalOrderComparator.java b/app/src/main/java/com/activeandroid/util/NaturalOrderComparator.java new file mode 100644 index 000000000..b09de26fa --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/NaturalOrderComparator.java @@ -0,0 +1,141 @@ +package com.activeandroid.util; + +/* + NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java. + Copyright (C) 2003 by Pierre-Luc Paour + + Based on the C version by Martin Pool, of which this is more or less a straight conversion. + Copyright (C) 2000 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + */ + +import java.util.Comparator; + +public class NaturalOrderComparator implements Comparator { + int compareRight(String a, String b) { + int bias = 0; + int ia = 0; + int ib = 0; + + // The longest run of digits wins. That aside, the greatest + // value wins, but we can't know that it will until we've scanned + // both numbers to know that they have the same magnitude, so we + // remember it in BIAS. + for (;; ia++, ib++) { + char ca = charAt(a, ia); + char cb = charAt(b, ib); + + if (!Character.isDigit(ca) && !Character.isDigit(cb)) { + return bias; + } + else if (!Character.isDigit(ca)) { + return -1; + } + else if (!Character.isDigit(cb)) { + return +1; + } + else if (ca < cb) { + if (bias == 0) { + bias = -1; + } + } + else if (ca > cb) { + if (bias == 0) + bias = +1; + } + else if (ca == 0 && cb == 0) { + return bias; + } + } + } + + public int compare(Object o1, Object o2) { + String a = o1.toString(); + String b = o2.toString(); + + int ia = 0, ib = 0; + int nza = 0, nzb = 0; + char ca, cb; + int result; + + while (true) { + // only count the number of zeroes leading the last number compared + nza = nzb = 0; + + ca = charAt(a, ia); + cb = charAt(b, ib); + + // skip over leading spaces or zeros + while (Character.isSpaceChar(ca) || ca == '0') { + if (ca == '0') { + nza++; + } + else { + // only count consecutive zeroes + nza = 0; + } + + ca = charAt(a, ++ia); + } + + while (Character.isSpaceChar(cb) || cb == '0') { + if (cb == '0') { + nzb++; + } + else { + // only count consecutive zeroes + nzb = 0; + } + + cb = charAt(b, ++ib); + } + + // process run of digits + if (Character.isDigit(ca) && Character.isDigit(cb)) { + if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) { + return result; + } + } + + if (ca == 0 && cb == 0) { + // The strings compare the same. Perhaps the caller + // will want to call strcmp to break the tie. + return nza - nzb; + } + + if (ca < cb) { + return -1; + } + else if (ca > cb) { + return +1; + } + + ++ia; + ++ib; + } + } + + static char charAt(String s, int i) { + if (i >= s.length()) { + return 0; + } + else { + return s.charAt(i); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/util/ReflectionUtils.java b/app/src/main/java/com/activeandroid/util/ReflectionUtils.java new file mode 100644 index 000000000..32e995cc3 --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/ReflectionUtils.java @@ -0,0 +1,110 @@ +package com.activeandroid.util; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.Set; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.serializer.TypeSerializer; + +public final class ReflectionUtils { + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public static boolean isModel(Class type) { + return isSubclassOf(type, Model.class) && (!Modifier.isAbstract(type.getModifiers())); + } + + public static boolean isTypeSerializer(Class type) { + return isSubclassOf(type, TypeSerializer.class); + } + + // Meta-data + + @SuppressWarnings("unchecked") + public static T getMetaData(Context context, String name) { + try { + final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + + if (ai.metaData != null) { + return (T) ai.metaData.get(name); + } + } + catch (Exception e) { + Log.w("Couldn't find meta-data: " + name); + } + + return null; + } + + public static Set getDeclaredColumnFields(Class type) { + Set declaredColumnFields = Collections.emptySet(); + + if (ReflectionUtils.isSubclassOf(type, Model.class) || Model.class.equals(type)) { + declaredColumnFields = new LinkedHashSet(); + + Field[] fields = type.getDeclaredFields(); + Arrays.sort(fields, new Comparator() { + @Override + public int compare(Field field1, Field field2) { + return field2.getName().compareTo(field1.getName()); + } + }); + for (Field field : fields) { + if (field.isAnnotationPresent(Column.class)) { + declaredColumnFields.add(field); + } + } + + Class parentType = type.getSuperclass(); + if (parentType != null) { + declaredColumnFields.addAll(getDeclaredColumnFields(parentType)); + } + } + + return declaredColumnFields; + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public static boolean isSubclassOf(Class type, Class superClass) { + if (type.getSuperclass() != null) { + if (type.getSuperclass().equals(superClass)) { + return true; + } + + return isSubclassOf(type.getSuperclass(), superClass); + } + + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/activeandroid/util/SQLiteUtils.java b/app/src/main/java/com/activeandroid/util/SQLiteUtils.java new file mode 100644 index 000000000..cbf41eaee --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/SQLiteUtils.java @@ -0,0 +1,406 @@ +package com.activeandroid.util; + +/* + * Copyright (C) 2010 Michael Pardo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.database.Cursor; +import android.os.Build; +import android.text.TextUtils; + +import com.activeandroid.Cache; +import com.activeandroid.Model; +import com.activeandroid.TableInfo; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Column.ConflictAction; +import com.activeandroid.serializer.TypeSerializer; + +import java.lang.Long; +import java.lang.String; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class SQLiteUtils { + ////////////////////////////////////////////////////////////////////////////////////// + // ENUMERATIONS + ////////////////////////////////////////////////////////////////////////////////////// + + public enum SQLiteType { + INTEGER, REAL, TEXT, BLOB + } + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC CONSTANTS + ////////////////////////////////////////////////////////////////////////////////////// + + public static final boolean FOREIGN_KEYS_SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE CONTSANTS + ////////////////////////////////////////////////////////////////////////////////////// + + @SuppressWarnings("serial") + private static final HashMap, SQLiteType> TYPE_MAP = new HashMap, SQLiteType>() { + { + put(byte.class, SQLiteType.INTEGER); + put(short.class, SQLiteType.INTEGER); + put(int.class, SQLiteType.INTEGER); + put(long.class, SQLiteType.INTEGER); + put(float.class, SQLiteType.REAL); + put(double.class, SQLiteType.REAL); + put(boolean.class, SQLiteType.INTEGER); + put(char.class, SQLiteType.TEXT); + put(byte[].class, SQLiteType.BLOB); + put(Byte.class, SQLiteType.INTEGER); + put(Short.class, SQLiteType.INTEGER); + put(Integer.class, SQLiteType.INTEGER); + put(Long.class, SQLiteType.INTEGER); + put(Float.class, SQLiteType.REAL); + put(Double.class, SQLiteType.REAL); + put(Boolean.class, SQLiteType.INTEGER); + put(Character.class, SQLiteType.TEXT); + put(String.class, SQLiteType.TEXT); + put(Byte[].class, SQLiteType.BLOB); + } + }; + + ////////////////////////////////////////////////////////////////////////////////////// + // PRIVATE MEMBERS + ////////////////////////////////////////////////////////////////////////////////////// + + private static HashMap> sIndexGroupMap; + private static HashMap> sUniqueGroupMap; + private static HashMap sOnUniqueConflictsMap; + + ////////////////////////////////////////////////////////////////////////////////////// + // PUBLIC METHODS + ////////////////////////////////////////////////////////////////////////////////////// + + public static void execSql(String sql) { + Cache.openDatabase().execSQL(sql); + } + + public static void execSql(String sql, Object[] bindArgs) { + Cache.openDatabase().execSQL(sql, bindArgs); + } + + public static List rawQuery(Class type, String sql, String[] selectionArgs) { + Cursor cursor = Cache.openDatabase().rawQuery(sql, selectionArgs); + List entities = processCursor(type, cursor); + cursor.close(); + + return entities; + } + + public static int intQuery(final String sql, final String[] selectionArgs) { + final Cursor cursor = Cache.openDatabase().rawQuery(sql, selectionArgs); + final int number = processIntCursor(cursor); + cursor.close(); + + return number; + } + + public static T rawQuerySingle(Class type, String sql, String[] selectionArgs) { + List entities = rawQuery(type, sql, selectionArgs); + + if (entities.size() > 0) { + return entities.get(0); + } + + return null; + } + + // Database creation + + public static ArrayList createUniqueDefinition(TableInfo tableInfo) { + final ArrayList definitions = new ArrayList(); + sUniqueGroupMap = new HashMap>(); + sOnUniqueConflictsMap = new HashMap(); + + for (Field field : tableInfo.getFields()) { + createUniqueColumnDefinition(tableInfo, field); + } + + if (sUniqueGroupMap.isEmpty()) { + return definitions; + } + + Set keySet = sUniqueGroupMap.keySet(); + for (String key : keySet) { + List group = sUniqueGroupMap.get(key); + ConflictAction conflictAction = sOnUniqueConflictsMap.get(key); + + definitions.add(String.format("UNIQUE (%s) ON CONFLICT %s", + TextUtils.join(", ", group), conflictAction.toString())); + } + + return definitions; + } + + public static void createUniqueColumnDefinition(TableInfo tableInfo, Field field) { + final String name = tableInfo.getColumnName(field); + final Column column = field.getAnnotation(Column.class); + + if (field.getName().equals("mId")) { + return; + } + + String[] groups = column.uniqueGroups(); + ConflictAction[] conflictActions = column.onUniqueConflicts(); + if (groups.length != conflictActions.length) + return; + + for (int i = 0; i < groups.length; i++) { + String group = groups[i]; + ConflictAction conflictAction = conflictActions[i]; + + if (TextUtils.isEmpty(group)) + continue; + + List list = sUniqueGroupMap.get(group); + if (list == null) { + list = new ArrayList(); + } + list.add(name); + + sUniqueGroupMap.put(group, list); + sOnUniqueConflictsMap.put(group, conflictAction); + } + } + + public static String[] createIndexDefinition(TableInfo tableInfo) { + final ArrayList definitions = new ArrayList(); + sIndexGroupMap = new HashMap>(); + + for (Field field : tableInfo.getFields()) { + createIndexColumnDefinition(tableInfo, field); + } + + if (sIndexGroupMap.isEmpty()) { + return new String[0]; + } + + for (Map.Entry> entry : sIndexGroupMap.entrySet()) { + definitions.add(String.format("CREATE INDEX IF NOT EXISTS %s on %s(%s);", + "index_" + tableInfo.getTableName() + "_" + entry.getKey(), + tableInfo.getTableName(), TextUtils.join(", ", entry.getValue()))); + } + + return definitions.toArray(new String[definitions.size()]); + } + + public static void createIndexColumnDefinition(TableInfo tableInfo, Field field) { + final String name = tableInfo.getColumnName(field); + final Column column = field.getAnnotation(Column.class); + + if (field.getName().equals("mId")) { + return; + } + + if (column.index()) { + List list = new ArrayList(); + list.add(name); + sIndexGroupMap.put(name, list); + } + + String[] groups = column.indexGroups(); + for (String group : groups) { + if (TextUtils.isEmpty(group)) + continue; + + List list = sIndexGroupMap.get(group); + if (list == null) { + list = new ArrayList(); + } + + list.add(name); + sIndexGroupMap.put(group, list); + } + } + + public static String createTableDefinition(TableInfo tableInfo) { + final ArrayList definitions = new ArrayList(); + + for (Field field : tableInfo.getFields()) { + String definition = createColumnDefinition(tableInfo, field); + if (!TextUtils.isEmpty(definition)) { + definitions.add(definition); + } + } + + definitions.addAll(createUniqueDefinition(tableInfo)); + + return String.format("CREATE TABLE IF NOT EXISTS %s (%s);", tableInfo.getTableName(), + TextUtils.join(", ", definitions)); + } + + @SuppressWarnings("unchecked") + public static String createColumnDefinition(TableInfo tableInfo, Field field) { + StringBuilder definition = new StringBuilder(); + + Class type = field.getType(); + final String name = tableInfo.getColumnName(field); + final TypeSerializer typeSerializer = Cache.getParserForType(field.getType()); + final Column column = field.getAnnotation(Column.class); + + if (typeSerializer != null) { + type = typeSerializer.getSerializedType(); + } + + if (TYPE_MAP.containsKey(type)) { + definition.append(name); + definition.append(" "); + definition.append(TYPE_MAP.get(type).toString()); + } + else if (ReflectionUtils.isModel(type)) { + definition.append(name); + definition.append(" "); + definition.append(SQLiteType.INTEGER.toString()); + } + else if (ReflectionUtils.isSubclassOf(type, Enum.class)) { + definition.append(name); + definition.append(" "); + definition.append(SQLiteType.TEXT.toString()); + } + + if (!TextUtils.isEmpty(definition)) { + + if (name.equals(tableInfo.getIdName())) { + definition.append(" PRIMARY KEY AUTOINCREMENT"); + }else if(column!=null){ + if (column.length() > -1) { + definition.append("("); + definition.append(column.length()); + definition.append(")"); + } + + if (column.notNull()) { + definition.append(" NOT NULL ON CONFLICT "); + definition.append(column.onNullConflict().toString()); + } + + if (column.unique()) { + definition.append(" UNIQUE ON CONFLICT "); + definition.append(column.onUniqueConflict().toString()); + } + } + + if (FOREIGN_KEYS_SUPPORTED && ReflectionUtils.isModel(type)) { + definition.append(" REFERENCES "); + definition.append(Cache.getTableInfo((Class) type).getTableName()); + definition.append("("+tableInfo.getIdName()+")"); + definition.append(" ON DELETE "); + definition.append(column.onDelete().toString().replace("_", " ")); + definition.append(" ON UPDATE "); + definition.append(column.onUpdate().toString().replace("_", " ")); + } + } + else { + Log.e("No type mapping for: " + type.toString()); + } + + return definition.toString(); + } + + @SuppressWarnings("unchecked") + public static List processCursor(Class type, Cursor cursor) { + TableInfo tableInfo = Cache.getTableInfo(type); + String idName = tableInfo.getIdName(); + final List entities = new ArrayList(); + + try { + Constructor entityConstructor = type.getConstructor(); + + if (cursor.moveToFirst()) { + /** + * Obtain the columns ordered to fix issue #106 (https://github.com/pardom/ActiveAndroid/issues/106) + * when the cursor have multiple columns with same name obtained from join tables. + */ + List columnsOrdered = new ArrayList(Arrays.asList(cursor.getColumnNames())); + do { + Model entity = Cache.getEntity(type, cursor.getLong(columnsOrdered.indexOf(idName))); + if (entity == null) { + entity = (T) entityConstructor.newInstance(); + } + + entity.loadFromCursor(cursor); + entities.add((T) entity); + } + while (cursor.moveToNext()); + } + + } + catch (NoSuchMethodException e) { + throw new RuntimeException( + "Your model " + type.getName() + " does not define a default " + + "constructor. The default constructor is required for " + + "now in ActiveAndroid models, as the process to " + + "populate the ORM model is : " + + "1. instantiate default model " + + "2. populate fields" + ); + } + catch (Exception e) { + Log.e("Failed to process cursor.", e); + } + + return entities; + } + + private static int processIntCursor(final Cursor cursor) { + if (cursor.moveToFirst()) { + return cursor.getInt(0); + } + return 0; + } + + public static List lexSqlScript(String sqlScript) { + ArrayList sl = new ArrayList(); + boolean inString = false, quoteNext = false; + StringBuilder b = new StringBuilder(100); + + for (int i = 0; i < sqlScript.length(); i++) { + char c = sqlScript.charAt(i); + + if (c == ';' && !inString && !quoteNext) { + sl.add(b.toString()); + b = new StringBuilder(100); + inString = false; + quoteNext = false; + continue; + } + + if (c == '\'' && !quoteNext) { + inString = !inString; + } + + quoteNext = c == '\\' && !quoteNext; + + b.append(c); + } + + if (b.length() > 0) { + sl.add(b.toString()); + } + + return sl; + } +} diff --git a/app/src/main/java/com/activeandroid/util/SqlParser.java b/app/src/main/java/com/activeandroid/util/SqlParser.java new file mode 100644 index 000000000..f9531b7c8 --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/SqlParser.java @@ -0,0 +1,110 @@ + +package com.activeandroid.util; + +/* + * Copyright (C) 2014 Markus Pfeiffer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + + +public class SqlParser { + + public final static int STATE_NONE = 0; + public final static int STATE_STRING = 1; + public final static int STATE_COMMENT = 2; + public final static int STATE_COMMENT_BLOCK = 3; + + public static List parse(final InputStream stream) throws IOException { + + final BufferedInputStream buffer = new BufferedInputStream(stream); + final List commands = new ArrayList(); + final StringBuffer sb = new StringBuffer(); + + try { + final Tokenizer tokenizer = new Tokenizer(buffer); + int state = STATE_NONE; + + while (tokenizer.hasNext()) { + final char c = (char) tokenizer.next(); + + if (state == STATE_COMMENT_BLOCK) { + if (tokenizer.skip("*/")) { + state = STATE_NONE; + } + continue; + + } else if (state == STATE_COMMENT) { + if (isNewLine(c)) { + state = STATE_NONE; + } + continue; + + } else if (state == STATE_NONE && tokenizer.skip("/*")) { + state = STATE_COMMENT_BLOCK; + continue; + + } else if (state == STATE_NONE && tokenizer.skip("--")) { + state = STATE_COMMENT; + continue; + + } else if (state == STATE_NONE && c == ';') { + final String command = sb.toString().trim(); + commands.add(command); + sb.setLength(0); + continue; + + } else if (state == STATE_NONE && c == '\'') { + state = STATE_STRING; + + } else if (state == STATE_STRING && c == '\'') { + state = STATE_NONE; + + } + + if (state == STATE_NONE || state == STATE_STRING) { + if (state == STATE_NONE && isWhitespace(c)) { + if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') { + sb.append(' '); + } + } else { + sb.append(c); + } + } + } + + } finally { + IOUtils.closeQuietly(buffer); + } + + if (sb.length() > 0) { + commands.add(sb.toString().trim()); + } + + return commands; + } + + private static boolean isNewLine(final char c) { + return c == '\r' || c == '\n'; + } + + private static boolean isWhitespace(final char c) { + return c == '\r' || c == '\n' || c == '\t' || c == ' '; + } +} diff --git a/app/src/main/java/com/activeandroid/util/Tokenizer.java b/app/src/main/java/com/activeandroid/util/Tokenizer.java new file mode 100644 index 000000000..8ae34da32 --- /dev/null +++ b/app/src/main/java/com/activeandroid/util/Tokenizer.java @@ -0,0 +1,76 @@ + +package com.activeandroid.util; + +/* + * Copyright (C) 2014 Markus Pfeiffer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.IOException; +import java.io.InputStream; + + +public class Tokenizer { + + private final InputStream mStream; + + private boolean mIsNext; + private int mCurrent; + + public Tokenizer(final InputStream in) { + this.mStream = in; + } + + public boolean hasNext() throws IOException { + + if (!this.mIsNext) { + this.mIsNext = true; + this.mCurrent = this.mStream.read(); + } + return this.mCurrent != -1; + } + + public int next() throws IOException { + + if (!this.mIsNext) { + this.mCurrent = this.mStream.read(); + } + this.mIsNext = false; + return this.mCurrent; + } + + public boolean skip(final String s) throws IOException { + + if (s == null || s.length() == 0) { + return false; + } + + if (s.charAt(0) != this.mCurrent) { + return false; + } + + final int len = s.length(); + this.mStream.mark(len - 1); + + for (int n = 1; n < len; n++) { + final int value = this.mStream.read(); + + if (value != s.charAt(n)) { + this.mStream.reset(); + return false; + } + } + return true; + } +} diff --git a/app/src/main/java/com/activeandroid/widget/ModelAdapter.java b/app/src/main/java/com/activeandroid/widget/ModelAdapter.java new file mode 100644 index 000000000..a38957636 --- /dev/null +++ b/app/src/main/java/com/activeandroid/widget/ModelAdapter.java @@ -0,0 +1,57 @@ +package com.activeandroid.widget; + +import java.util.Collection; +import java.util.List; + +import android.content.Context; +import android.widget.ArrayAdapter; + +import com.activeandroid.Model; + +public class ModelAdapter extends ArrayAdapter { + public ModelAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + } + + public ModelAdapter(Context context, int resource, int textViewResourceId) { + super(context, resource, textViewResourceId); + } + + public ModelAdapter(Context context, int textViewResourceId, List objects) { + super(context, textViewResourceId, objects); + } + + public ModelAdapter(Context context, int resource, int textViewResourceId, List objects) { + super(context, resource, textViewResourceId, objects); + } + + /** + * Clears the adapter and, if data != null, fills if with new Items. + * + * @param collection A Collection<? extends T> which members get added to the adapter. + */ + public void setData(Collection collection) { + clear(); + + if (collection != null) { + for (T item : collection) { + add(item); + } + } + } + + /** + * @return The Id of the record at position. + */ + @Override + public long getItemId(int position) { + T item = getItem(position); + + if (item != null) { + return item.getId(); + } + else { + return -1; + } + } +}