package com.smartandroid.sa.sql; /* * 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.ArrayList; import java.util.Arrays; import java.util.List; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.smartandroid.sa.sql.content.ContentProvider; import com.smartandroid.sa.sql.query.Delete; import com.smartandroid.sa.sql.query.Select; import com.smartandroid.sa.sql.serializer.TypeSerializer; import com.smartandroid.sa.sql.util.Log; import com.smartandroid.sa.sql.util.ReflectionUtils; @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); Cache.getContext() .getContentResolver() .notifyChange( ContentProvider.createUri(mTableInfo.getType(), mId), null); } 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); } Cache.getContext() .getContentResolver() .notifyChange( ContentProvider.createUri(mTableInfo.getType(), mId), null); return mId; } // Convenience methods public static void delete(Class<? extends Model> type, long id) { TableInfo tableInfo = Cache.getTableInfo(type); new Delete().from(type).where(tableInfo.getIdName() + "=?", id) .execute(); } public static <T extends Model> T load(Class<T> 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<String> columnsOrdered = new ArrayList<String>( 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<? extends Model> entityType = (Class<? extends Model>) 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<? extends Enum> enumType = (Class<? extends Enum>) 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 <T extends Model> List<T> getMany(Class<T> 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. } }