diff --git a/src/com/activeandroid/Model.java b/src/com/activeandroid/Model.java index 421426ea3..482fa71dc 100644 --- a/src/com/activeandroid/Model.java +++ b/src/com/activeandroid/Model.java @@ -20,6 +20,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import com.activeandroid.annotation.Table; import com.activeandroid.content.ContentProvider; import com.activeandroid.query.Delete; import com.activeandroid.query.Select; @@ -81,85 +82,99 @@ public final Long save() { 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); - } + // Skip the ID column + if (!fieldName.equals(idName)) { + 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); + // Check if the custom id name is defined + if (!mTableInfo.getCustomIdName().equals(Table.DEFAULT_CUSTOM_ID_NAME)) { + + String customIdValue = values.get(mTableInfo.getCustomIdName()).toString(); + + Cursor cursor = db.query(mTableInfo.getTableName(), null, mTableInfo.getCustomIdName() + " = ?", + new String[]{customIdValue}, null, null, null, "1"); + + // Check if a row already exists + if (cursor != null && cursor.getCount() > 0) { + + cursor.moveToFirst(); + mId = cursor.getLong(cursor.getColumnIndex(idName)); + + db.update(mTableInfo.getTableName(), values, mTableInfo.getCustomIdName() + " = ?", + new String[]{customIdValue}); + } + else { + mId = db.insert(mTableInfo.getTableName(), null, values); + } + + cursor.close(); + } + else { + 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); + .notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null); return mId; } @@ -303,7 +318,7 @@ public boolean equals(Object obj) { if (obj instanceof Model && this.mId != null) { final Model other = (Model) obj; - return this.mId.equals(other.mId) + return this.mId.equals(other.mId) && (this.mTableInfo.getTableName().equals(other.mTableInfo.getTableName())); } else { return this == obj; diff --git a/src/com/activeandroid/TableInfo.java b/src/com/activeandroid/TableInfo.java index 32d1ecb3f..33df0a733 100644 --- a/src/com/activeandroid/TableInfo.java +++ b/src/com/activeandroid/TableInfo.java @@ -16,6 +16,14 @@ * limitations under the License. */ +import android.text.TextUtils; +import android.util.Log; + +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; +import com.activeandroid.util.ReflectionUtils; +import com.activeandroid.util.SQLiteUtils; + import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; @@ -24,13 +32,6 @@ 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 @@ -39,8 +40,10 @@ public final class TableInfo { private Class mType; private String mTableName; private String mIdName = Table.DEFAULT_ID_NAME; + private String mCustomIdName = Table.DEFAULT_CUSTOM_ID_NAME; + - private Map mColumnNames = new LinkedHashMap(); + private Map mColumnNames = new LinkedHashMap(); ////////////////////////////////////////////////////////////////////////////////////// // CONSTRUCTORS @@ -54,6 +57,7 @@ public TableInfo(Class type) { if (tableAnnotation != null) { mTableName = tableAnnotation.name(); mIdName = tableAnnotation.id(); + mCustomIdName = tableAnnotation.customIdName(); } else { mTableName = type.getSimpleName(); @@ -66,6 +70,9 @@ public TableInfo(Class type) { List fields = new LinkedList(ReflectionUtils.getDeclaredColumnFields(type)); Collections.reverse(fields); + boolean isCustomIdSupplied = !mCustomIdName.equals(Table.DEFAULT_CUSTOM_ID_NAME); + Field customIdField = null; + for (Field field : fields) { if (field.isAnnotationPresent(Column.class)) { final Column columnAnnotation = field.getAnnotation(Column.class); @@ -74,12 +81,30 @@ public TableInfo(Class type) { columnName = field.getName(); } + if (isCustomIdSupplied && columnName.equals(mCustomIdName)) { + customIdField = field; + } + mColumnNames.put(field, columnName); } } + if (isCustomIdSupplied) { + if (customIdField == null) { + Log.e(com.activeandroid.util.Log.sTag, + "Given custom Id doesn't exists in table columns", + new Throwable("Custom Id defined as " + mCustomIdName + ", but doesn't exists in table columns.")); + } + else if (!SQLiteUtils.TYPE_MAP.containsKey(customIdField.getType())) { + Log.e(com.activeandroid.util.Log.sTag, + "Given custom Id is of an illegal type", + new Throwable("Custom Id type " + customIdField.getType() + " isn't a legal id type")); + } + } } + + ////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ////////////////////////////////////////////////////////////////////////////////////// @@ -96,7 +121,11 @@ public String getIdName() { return mIdName; } - public Collection getFields() { + public String getCustomIdName() { + return mCustomIdName; + } + + public Collection getFields() { return mColumnNames.keySet(); } @@ -104,7 +133,6 @@ public String getColumnName(Field field) { return mColumnNames.get(field); } - private Field getIdField(Class type) { if (type.equals(Model.class)) { try { diff --git a/src/com/activeandroid/annotation/Table.java b/src/com/activeandroid/annotation/Table.java index 541dfbe92..993e7bffc 100644 --- a/src/com/activeandroid/annotation/Table.java +++ b/src/com/activeandroid/annotation/Table.java @@ -28,4 +28,7 @@ public static final String DEFAULT_ID_NAME = "Id"; public String name(); public String id() default DEFAULT_ID_NAME; + + public static final String DEFAULT_CUSTOM_ID_NAME = ""; + public String customIdName() default DEFAULT_CUSTOM_ID_NAME; } diff --git a/src/com/activeandroid/util/Log.java b/src/com/activeandroid/util/Log.java index 3c40a23f5..38aa52c08 100644 --- a/src/com/activeandroid/util/Log.java +++ b/src/com/activeandroid/util/Log.java @@ -21,7 +21,7 @@ public final class Log { // PUBLIC MEMBERS ////////////////////////////////////////////////////////////////////////////////////// - private static String sTag = "ActiveAndroid"; + public static final String sTag = "ActiveAndroid"; private static boolean sEnabled = false; ////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/com/activeandroid/util/SQLiteUtils.java b/src/com/activeandroid/util/SQLiteUtils.java index cbf41eaee..51550a3bf 100644 --- a/src/com/activeandroid/util/SQLiteUtils.java +++ b/src/com/activeandroid/util/SQLiteUtils.java @@ -58,7 +58,7 @@ public enum SQLiteType { ////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("serial") - private static final HashMap, SQLiteType> TYPE_MAP = new HashMap, SQLiteType>() { + public static final HashMap, SQLiteType> TYPE_MAP = new HashMap, SQLiteType>() { { put(byte.class, SQLiteType.INTEGER); put(short.class, SQLiteType.INTEGER);