package com.external.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.external.activeandroid.Cache; import com.external.activeandroid.Model; import com.external.activeandroid.TableInfo; import com.external.activeandroid.annotation.Column; import com.external.activeandroid.serializer.TypeSerializer; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; 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<Class<?>, SQLiteType> TYPE_MAP = new HashMap<Class<?>, 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); } }; ////////////////////////////////////////////////////////////////////////////////////// // 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 <T extends Model> List<T> rawQuery(Class<? extends Model> type, String sql, String[] selectionArgs) { Cursor cursor = Cache.openDatabase().rawQuery(sql, selectionArgs); List<T> entities = processCursor(type, cursor); cursor.close(); return entities; } public static <T extends Model> T rawQuerySingle(Class<? extends Model> type, String sql, String[] selectionArgs) { List<T> entities = rawQuery(type, sql, selectionArgs); if (entities.size() > 0) { return entities.get(0); } return null; } // Database creation public static String createTableDefinition(TableInfo tableInfo) { final ArrayList<String> definitions = new ArrayList<String>(); for (Field field : tableInfo.getFields()) { String definition = createColumnDefinition(tableInfo, field); if (!TextUtils.isEmpty(definition)) { definitions.add(definition); } } 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) { String definition = null; 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 = name + " " + TYPE_MAP.get(type).toString(); } else if (ReflectionUtils.isModel(type)) { definition = name + " " + SQLiteType.INTEGER.toString(); } else if (ReflectionUtils.isSubclassOf(type, Enum.class)) { definition = name + " " + SQLiteType.TEXT.toString(); } if (definition != null) { if (column.length() > -1) { definition += "(" + column.length() + ")"; } if (name.equals("Id")) { definition += " PRIMARY KEY AUTOINCREMENT"; } if (column.notNull()) { definition += " NOT NULL ON CONFLICT " + column.onNullConflict().toString(); } if (column.unique()) { definition += " UNIQUE ON CONFLICT " + column.onUniqueConflict().toString(); } if (FOREIGN_KEYS_SUPPORTED && ReflectionUtils.isModel(type)) { definition += " REFERENCES " + Cache.getTableInfo((Class<? extends Model>) type).getTableName() + "(Id)"; definition += " ON DELETE " + column.onDelete().toString().replace("_", " "); definition += " ON UPDATE " + column.onUpdate().toString().replace("_", " "); } } else { Log.e("No type mapping for: " + type.toString()); } return definition; } @SuppressWarnings("unchecked") public static <T extends Model> List<T> processCursor(Class<? extends Model> type, Cursor cursor) { final List<T> entities = new ArrayList<T>(); try { Constructor<?> entityConstructor = type.getConstructor(); if (cursor.moveToFirst()) { do { // TODO: Investigate entity cache leak T entity = (T) entityConstructor.newInstance(); ((Model) entity).loadFromCursor(type, cursor); entities.add(entity); } while (cursor.moveToNext()); } } catch (Exception e) { Log.e("Failed to process cursor.", e); } return entities; } }