From 7cb185a446df5fb3debb74aefb5b6b085012eaca Mon Sep 17 00:00:00 2001 From: Ross Bamford Date: Tue, 2 Jul 2013 02:12:53 +0100 Subject: [PATCH] Address issue #10: * Give more meaningful exceptions from Entity#load() * Remove default type mapping (it doesn't work with the new load API) * Add mapping for java.util.Date. Thanks to rothschild86 for this bug report. --- .../roscopeco/ormdroid/DateTypeMapping.java | 53 +++++++++++++++++++ src/com/roscopeco/ormdroid/Entity.java | 17 ++++-- src/com/roscopeco/ormdroid/TypeMapper.java | 17 +++--- 3 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 src/com/roscopeco/ormdroid/DateTypeMapping.java diff --git a/src/com/roscopeco/ormdroid/DateTypeMapping.java b/src/com/roscopeco/ormdroid/DateTypeMapping.java new file mode 100644 index 0000000..12a68fa --- /dev/null +++ b/src/com/roscopeco/ormdroid/DateTypeMapping.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Ross Bamford + * + * 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. + */ +package com.roscopeco.ormdroid; + +import java.util.Date; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +/* + * Map java.util.Date to the database. + * + * This implementation just stashes the number of seconds + * since the epoch in a BIGINT. + */ +public class DateTypeMapping implements TypeMapping { + private Class mJavaType; + private String mSqlType; + + public DateTypeMapping() { + mJavaType = Date.class; + mSqlType = "BIGINT"; + } + + public Class javaType() { + return mJavaType; + } + + public String sqlType(Class concreteType) { + return mSqlType; + } + + public String encodeValue(SQLiteDatabase db, Object value) { + return "\"" + ((Date)value).getTime() + "\""; + } + + public Object decodeValue(SQLiteDatabase db, Class expectedType, Cursor c, int columnIndex) { + return new Date(c.getInt(columnIndex)); + } +} \ No newline at end of file diff --git a/src/com/roscopeco/ormdroid/Entity.java b/src/com/roscopeco/ormdroid/Entity.java index cb6591a..3d60a41 100644 --- a/src/com/roscopeco/ormdroid/Entity.java +++ b/src/com/roscopeco/ormdroid/Entity.java @@ -174,7 +174,6 @@ static final class EntityMapping { static EntityMapping build(Class clz) { EntityMapping mapping = new EntityMapping(); mapping.mMappedClass = clz; - Table table = clz.getAnnotation(Table.class); if (table != null) { mapping.mTableName = table.name(); @@ -207,6 +206,15 @@ static EntityMapping build(Class clz) { (!Modifier.isPrivate(modifiers) || force) && !seenFields.contains(f.getName()) && !inverse) { + + // Check we can map this type - if not, let's fail fast. + // This will save us wierd exceptions somewhere down the line... + if (TypeMapper.getMapping(f.getType()) == null) { + throw new TypeMappingException("Model " + + clz.getName() + + " has unmappable field: " + f); + } + Column col = f.getAnnotation(Column.class); String name; @@ -466,12 +474,15 @@ T load(SQLiteDatabase db, Cursor c) { } return model; - } catch (Exception e) { + } catch (InstantiationException e) { throw new ORMDroidException( "Failed to instantiate model class - does it have a public null constructor?", e); + } catch (IllegalAccessException e) { + throw new ORMDroidException( + "Access denied. Is your model's constructor non-public?", + e); } - } /* diff --git a/src/com/roscopeco/ormdroid/TypeMapper.java b/src/com/roscopeco/ormdroid/TypeMapper.java index 6f5675f..f1a8de5 100644 --- a/src/com/roscopeco/ormdroid/TypeMapper.java +++ b/src/com/roscopeco/ormdroid/TypeMapper.java @@ -22,8 +22,9 @@ * *

By default, ORMDroid provides a mapping for all primitive types * and Entity classes. All other types are mapped by the default - * mapping, which simply stores their toString results - * in a VARCHAR column.

+ * mapping (if configured with {@link #setDefaultMapping(TypeMapping)}). + * By default, there is no default mapping - attempting to use a model class + * with a field of an unmapped type will throw an exception.

* *

Custom types may be mapped by registering an instance of * {@link TypeMapping} via the {@link #mapType(TypeMapping) mapType} method. @@ -41,7 +42,7 @@ */ public final class TypeMapper { private static final MappingList TYPEMAPS = new MappingList(); - private static TypeMapping mDefaultMapping = new StringTypeMapping(Object.class, "VARCHAR"); + private static TypeMapping mDefaultMapping = null; public static String sqlType(Class type) { return getMapping(type).sqlType(type); @@ -51,19 +52,14 @@ public static String sqlType(Class type) { * Obtain the configured mapping the the specified Java type. * * @param type the Java type. - * @return the configured mapping. + * @return the configured mapping, or null if none. */ public static TypeMapping getMapping(Class type) { TypeMapping r = TYPEMAPS.findMapping(type); if (r != null) { return r; } else { - TypeMapping def = mDefaultMapping; - if (def != null) { - return def; - } else { - throw new TypeMappingException("No mapping found for type `" + type + "'"); - } + return mDefaultMapping; } } @@ -115,6 +111,7 @@ public static void setDefaultMapping(TypeMapping mapping) { mapType(new NumericTypeMapping(boolean.class, "TINYINT")); mapType(new NumericTypeMapping(Long.class, "BIGINT")); mapType(new NumericTypeMapping(long.class, "BIGINT")); + mapType(new DateTypeMapping()); mapType(new EntityTypeMapping()); mapType(new NumericTypeMapping(Integer.class, "INTEGER")); mapType(new NumericTypeMapping(int.class, "INTEGER"));