Android Open Source - ormdroid Entity






From Project

Back to project page ormdroid.

License

The source code is released under:

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUC...

If you think the Android project ormdroid listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2012 Ross Bamford/* w  ww .j av a  2  s  .c om*/
 *
 * 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 android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

/**
 * <p>Base class for persistent entities. The only hard requirements
 * for model classes are that they subclass this class, and that
 * they provide a (currently integral) primary key (see below).</p>
 * 
 * <p><code>Entity</code> is the primary class in ORMDroid, and is
 * where most interaction with the API will take place. A model class
 * will subclass this class, and will inherit its save() and delete()
 * methods from it. The simplest possible model class would be:</p>
 * 
 * <p><pre><code>
 * public class Person extends Entity {
 *   public int id;
 * }
 * </pre></code></p>
 * 
 * <p>This is obviously useless, as it holds no data other than the
 * (required) primary key. In order to actually store other data,
 * you would add further public fields. Entities may also define 
 * any methods you wish - these are never called by the framework</p>
 * 
 * <h3>Table & column names</h3>
 * 
 * <p>By default, the framework creates table names based on the 
 * fully-qualified name of the entity class. You can change this
 * behaviour by applying the {@link Table} annotation to the class,
 * e.g:</p>
 * 
 * <p><pre><code>
 * {@literal @}Table(name = "people")
 * public class Person extends Entity {
 *   // ...
 * }
 * </pre></code></p>
 * 
 * <p>Similarly, any column can be explicitly named using the
 * {@link Column} annotation:</p>
 * 
 * <p><pre><code>
 *   {@literal @}Column(name = "person_name")
 *   public String name;
 * </pre></code></p>
 *  
 * <h3>Primary key field</h3>
 * 
 * <p>In the example above, the framework automatically selects the 
 * primary key field based on it's name. Currently, the framework
 * will use the first field it finds named 'id' or '_id', and will
 * map these fields to database column '_id' in any case, unless
 * the field is explicitly named (see above).</p>
 * 
 * <p>It is possible to explicitly select the primary key column
 * using the {@link Column} annotation:
 * 
 * <p><pre><code>
 *   {@literal @}Column(primaryKey = true)
 *   public int myPrimaryKey;
 * </pre></code></p>
 * 
 * <p>It should be noted that, although you can use any data type
 * for primary key fields, parts of the framework currently expect
 * primary keys to be integers, and will behave in an indeterminite
 * manner if other types are used. This limitation primarily affects
 * the {@link Query} class, and the {@link #equals(Object)} and 
 * {@link #hashCode()} implementations defined in this class, and
 * will be removed in a future version.</p>
 * 
 * <h3>Private fields</h3>
 * 
 * By default, private fields are ignored when mapping model classes.
 * It is possible to force them to be mapped, however, using the
 * {@link Column} annotation
 * 
 * <p><pre><code>
 *   {@literal @}Column(forceMap = true)
 *   private String myPrivateField;
 * </pre></code></p>
 * 
 * <h3>Relationships</h3>
 * 
 * <p>The framework currently provides built-in support for 
 * one-to-one relationships - simply adding a field of an 
 * <code>Entity</code>-subclass type will cause that field to
 * be persisted when the containing object is persisted.</p>
 * 
 * <p>Many relationships are not currently natively supported,
 * but can easily be implemented using helper methods on your
 * model class. For example (taken from the ORMSample app):</p>
 * 
 * <p><pre><code>
 * public List<Person> people() {
 *   return query(Person.class).where("department").eq(id).executeMulti();
 * }
 * </pre></code></p>
 * 
 * <p>More support for such relationships (including entity type mappings
 * for {@link java.util.List} and {@link java.util.Map}) will be added in a 
 * future version.</p>
 * 
 * <p>Using the
 * <code>{@literal @}{@link Column}(inverse = "otherFieldName")</code>
 * annotation will prevent the field from being persisted when its
 * model is stored.</p>
 * 
 * <h3>Model lifecycle</h3>
 * 
 * <p>The typical lifecycle of a model is shown below:</p>
 * 
 * <p><pre><code>
 * MyModel m = new MyModel();
 *   // ... or ... 
 * MyModel m = Entity.query(MyModel).whereId().eq(1).execute();
 * 
 *   // ... do some work ...
 *   
 * MyModel.save();
 *   // ... or ...
 * MyModel.delete();
 * </pre></code></p>
 * 
 * <h3>{@link #equals} and {@link #hashCode}</h3>
 * 
 * <p>The default implementation of equals and hashCode provided
 * by this class define equality in terms of primary key, and 
 * utilise reflective field access. You may of course override 
 * these if you wish to change their behaviour, or for performance
 * reasons (e.g. to directly compare primary key fields rather than
 * using reflection).</p>
 */
public abstract class Entity {
  static final class EntityMapping {
    private static final String TAG = "INTERNAL<EntityMapping>";
    private static final Pattern MATCH_DOTDOLLAR = Pattern.compile("[\\.\\$]");
    
    private Class<? extends Entity> mMappedClass;
    String mTableName;
    private Field mPrimaryKey;
    String mPrimaryKeyColumnName;
    private ArrayList<String> mColumnNames = new ArrayList<String>();
      private ArrayList<Field> mFields = new ArrayList<Field>();
      private ArrayList<Field> mInverseFields = new ArrayList<Field>();
      private ArrayList<Field> mIndexFields = new ArrayList<Field>();
    boolean mSchemaCreated = false;

    // Not concerned too much about reflective annotation access in this
    // method, since this only runs once per model class...
    static EntityMapping build(Class<? extends Entity> clz) {
      EntityMapping mapping = new EntityMapping();
      mapping.mMappedClass = clz;
      Table table = clz.getAnnotation(Table.class);
      if (table != null) {
        mapping.mTableName = table.name();
      } else {
        mapping.mTableName = MATCH_DOTDOLLAR.matcher(clz.getName()).replaceAll("");
      }

      // Use a raw Class var to spin through the hierarchy, to avoid
      // type safety warnings which we know are not an issue.
      @SuppressWarnings("rawtypes")
      Class cClz = clz; 
      
      ArrayList<String> seenFields = new ArrayList<String>();
      
      // Loop through the class hierarchy, picking up the fields we'll be 
      // mapping as we go. 
      while (!Entity.class.getName().equals(cClz.getName())) {
        for (Field f : cClz.getDeclaredFields()) {
          f.setAccessible(true);
        
          // Blithely ignore this field if we've already seen one with same name -
          // Java field hiding allows this to happen and if it does, without this
          // we'd be adding the same column name twice.
          //
          // We might as well also ignore it here if it's inverse, since we'll
          // never want to access it via the mapping.
          //
          // Also, ignore statics/finals (bug #4)
          //
          // We ignore private fields, *unless* they're annotated with
          // the force attribute.
          Column colAnn = f.getAnnotation(Column.class);
          boolean inverse = colAnn != null && !colAnn.inverse().equals("");
          boolean force = colAnn != null && colAnn.forceMap();
          boolean index = colAnn != null && colAnn.index();
  
          int modifiers = f.getModifiers();
          if (!Modifier.isStatic(modifiers) &&
              !Modifier.isFinal(modifiers) &&
              (!Modifier.isPrivate(modifiers) || force) &&
              !seenFields.contains(f.getName())) {

            seenFields.add(f.getName());
            
            if (index) {
              mapping.mIndexFields.add(f);
            }

            if(!inverse) {

              // Check we can map this type - if not, let's fail fast.
              // This will save us weird exceptions somewhere down the line...
              if (TypeMapper.getMapping(f.getType()) == null) {
                throw new TypeMappingException("Model " +
                                               cClz.getName() +
                                               " has unmappable field: " + f);
              }

              // TODO basic type Lists ought to be okay here, but they are not implemented yet
              if(List.class.isAssignableFrom(f.getType())) {
                  throw new TypeMappingException("Model " +
                          cClz.getName() +
                          " field must be declared inverse: " + f);
              }

              Column col = f.getAnnotation(Column.class);
              String name;

              if (col != null) {
                  // empty is default, means we should use field name...
                  if ("".equals(name = col.name())) {
                      name = f.getName();
                  }

                if (col.primaryKey()) {
                  mapping.mPrimaryKey = f;
                  mapping.mPrimaryKeyColumnName = name;
                }
              } else {
                name = f.getName();
              }

              // Try to default primary key if we don't have one yet...
              if (mapping.mPrimaryKey == null) {
                if ("_id".equals(name) || "id".equals(name)) {
                  mapping.mPrimaryKey = f;
                  mapping.mPrimaryKeyColumnName = name;
                }
              }

              mapping.mColumnNames.add(name);
              mapping.mFields.add(f);
            }
            else {
              mapping.mInverseFields.add(f);
            }
          }
        }
        
        cClz = cClz.getSuperclass();
      }

      if (mapping.mPrimaryKey == null) {
        // Error at this point - we must have a primary key!
        Log.e(TAG,
            "No primary key specified or determined for " + clz);
        throw new ORMDroidException(
            "No primary key was specified, and a default could not be determined for " + clz);
      }

      return mapping;
    }

    void createSchema(SQLiteDatabase db) {
      StringBuilder b = new StringBuilder();
      b.append("CREATE TABLE IF NOT EXISTS ").append(mTableName).append(" (");

      int len = mFields.size();
      for (int i = 0; i < len; i++) {
        String colName = mColumnNames.get(i);

        // Without this we'll add overriden fields twice...
        b.append(colName);
        b.append(" ");
        b.append(TypeMapper.sqlType(mFields.get(i).getType()));
        if (colName.equals(mPrimaryKeyColumnName)) {
          b.append(" PRIMARY KEY AUTOINCREMENT");
        }

        if (i < len - 1) {
          b.append(",");
        }
      }

      b.append(");");

      String sql = b.toString();
      Log.v(TAG, sql);
      db.execSQL(sql);
      
      for (int i = 0; i < len; i++) {
        Field f = mFields.get(i);
        if (mIndexFields.contains(f)) {
          String colName = mColumnNames.get(i);
          String sqlIndex = new StringBuilder().append("CREATE INDEX IF NOT EXISTS ").append(mTableName).append("_").append(colName).append("_index ON ").append(mTableName).append("(").append(colName).append(");").toString();
          Log.v(TAG, sqlIndex);
            db.execSQL(sqlIndex);
        }
      }
      
      mSchemaCreated = true;
    }

    private boolean isPrimaryKey(Field f) {
      return mPrimaryKey.equals(f);
    }

    Object getPrimaryKeyValue(Entity o) {
      try {
        return mPrimaryKey.get(o);
      } catch (IllegalAccessException e) {
        Log.e(TAG,
            "IllegalAccessException accessing primary key " + mPrimaryKey
                + "; Update failed");
        throw new ORMDroidException(
            "IllegalAccessException accessing primary key " + mPrimaryKey
                + "; Update failed");
      }
    }

    private void setPrimaryKeyValue(Entity o, Object value) {
      try {
        mPrimaryKey.set(o, value);
      } catch (IllegalAccessException e) {
        Log.e(TAG,
            "IllegalAccessException accessing primary key " + mPrimaryKey
                + "; Update failed");
        throw new ORMDroidException(
            "IllegalAccessException accessing primary key " + mPrimaryKey
                + "; Update failed");
      }
    }

    private String processValue(SQLiteDatabase db, Object value) {
      return TypeMapper.encodeValue(db, value);
    }

    private String getColNames() {
      StringBuilder b = new StringBuilder();
      ArrayList<String> names = mColumnNames;
      ArrayList<Field> fields = mFields;
      int len = names.size();

      for (int i = 0; i < len; i++) {
        Field f = fields.get(i);
        if (!isPrimaryKey(f)) {
          b.append(names.get(i));
          
          if (i < len-1) {
            b.append(",");
          }

        }
      }

      return b.toString();
    }

    private String getFieldValues(SQLiteDatabase db, Entity receiver) {
      StringBuilder b = new StringBuilder();
      ArrayList<Field> fields = mFields;
      int len = fields.size();

      for (int i = 0; i < len; i++) {
        Field f = fields.get(i);
        if (!isPrimaryKey(f)) {
          Object val;
          try {
            val = f.get(receiver);
          } catch (IllegalAccessException e) {
            // Should never happen...
            Log.e(TAG,
                "IllegalAccessException accessing field "
                    + fields.get(i).getName() + "; Inserting NULL");
            val = null;
          }
          
          b.append(val == null ? "null" : processValue(db, val));

          if (i < len-1) {
            b.append(",");
          }
        }
      }

      return b.toString();
    }

    private String getSetFields(SQLiteDatabase db, Object receiver) {
      StringBuilder b = new StringBuilder();
      ArrayList<String> names = mColumnNames;
      ArrayList<Field> fields = mFields;
      int len = names.size();

      for (int i = 0; i < len; i++) {
        Field f = fields.get(i);
        String name = names.get(i);

        // We don't want to set the primary key...
        if (name != mPrimaryKeyColumnName) {
          b.append(name);
          b.append("=");
          Object val;
          try {
            val = f.get(receiver);
          } catch (IllegalAccessException e) {
            Log.w(TAG,
                "IllegalAccessException accessing field "
                    + fields.get(i).getName() + "; Inserting NULL");
            val = null;
          }
          b.append(val == null ? "null" : processValue(db, val));
          if (i < (len - 1)) {
            b.append(",");
          }
        }
      }

      return b.toString();
    }
    
    /* issue #6 */
    private String stripTrailingComma(String string) {
      // check for last comma
      if (string.endsWith(",")) {
        return string.substring(0, string.length() - 1);
      }
      return string;
    }

    int insert(SQLiteDatabase db, Entity o) {
      String sql = "INSERT INTO " + mTableName + " ("
          + stripTrailingComma(getColNames()) + ") VALUES ("
          + stripTrailingComma(getFieldValues(db, o)) + ")";

      Log.v(getClass().getSimpleName(), sql);

      db.execSQL(sql);

      Cursor c = db.rawQuery("select last_insert_rowid();", null);
      try {
      if (c.moveToFirst()) {
        Integer i = c.getInt(0);
          setPrimaryKeyValue(o, i);
        return i;
      } else {
        throw new ORMDroidException("Failed to get last inserted id after INSERT");
      }
      } finally {
        c.close();
      }
    }

    void update(SQLiteDatabase db, Entity o) {
      // stripTrailingComma: issue #9
      String sql = "UPDATE " + mTableName + " SET " + stripTrailingComma(getSetFields(db, o))
          + " WHERE " + mPrimaryKeyColumnName + "=" + getPrimaryKeyValue(o);

      Log.v(getClass().getSimpleName(), sql);

      db.execSQL(sql);
    }

    /*
     * Doesn't move the cursor - expects it to be positioned appropriately.
     */
    <T extends Entity> T load(SQLiteDatabase db, Cursor c) {
        return load(db, c, null);
    }

    <T extends Entity> T load(SQLiteDatabase db, Cursor c, ArrayList<T> precursors) {
      try {
        // TODO we should be checking here that we've got data before
        // instantiating...
        @SuppressWarnings("unchecked")
        T model = (T) mMappedClass.newInstance();
        model.mTransient = false;

        ArrayList<String> colNames = mColumnNames;
        ArrayList<Field> fields = mFields;
        int len = colNames.size();

        int pkColIndex = c.getColumnIndex(mPrimaryKeyColumnName);

        {
          Field f = mPrimaryKey;
          Class<?> ftype = f.getType();

          if (pkColIndex == -1) {
            Log.e("Internal<ModelMapping>", "Got -1 column index for `"+mPrimaryKeyColumnName+"' - Database schema may not match entity");
            throw new ORMDroidException("Got -1 column index for `"+mPrimaryKeyColumnName+"' - Database schema may not match entity");
          } else {
            Object o = TypeMapper.getMapping(ftype).decodeValue(db, f, c, pkColIndex, precursors);
            f.set(model, o);
          }
        }

        if(precursors != null) {
          int index = precursors.indexOf(model);

          Log.d("ListTypeMapping", "found = " + index + " for " + precursors.size() + " precursor(s)");

          if(index > -1) {
            T precursor = precursors.get(index);

            return precursor;
          }
        }
        else {
          precursors = new ArrayList<T>();
        }

        precursors.add(model);

        for (int i = 0; i < len; i++) {
          Field f = fields.get(i);
          Class<?> ftype = f.getType();
          int colIndex = c.getColumnIndex(colNames.get(i));

          if(colIndex == pkColIndex)
            continue;

          if (colIndex == -1) {
            Log.e("Internal<ModelMapping>", "Got -1 column index for `"+colNames.get(i)+"' - Database schema may not match entity");
            throw new ORMDroidException("Got -1 column index for `"+colNames.get(i)+"' - Database schema may not match entity");
          } else {
            Object o = TypeMapper.getMapping(ftype).decodeValue(db, f, c, colIndex, precursors);
            f.set(model, o);
          }
        }

        // Inverse fields are not loaded from database
        // The Primary Key is supplied in place of the column index

        len = mInverseFields.size();
          fields = mInverseFields;

        for(int i = 0; i < len; i++) {
          Field f = fields.get(i);
          Class<?> ftype = f.getType();

          Object o = TypeMapper.getMapping(ftype).decodeValue(db, f, c, (Integer) model.getPrimaryKeyValue(), precursors);
          f.set(model, o);
        }

        return model;
      } 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);
      }
    }

    /*
     * Moves cursor to start, and runs through all records.
     */
    <T extends Entity> List<T> loadAll(SQLiteDatabase db, Cursor c) {
      ArrayList<T> list = new ArrayList<T>();

      if (c.moveToFirst()) {
        do {
          list.add(this.<T> load(db, c));
        } while (c.moveToNext());
      }
      
      /* issue #6 */
      c.close();      

      return list;
    }
    
    void delete(SQLiteDatabase db, Entity o) {
      String sql = "DELETE FROM " + mTableName + " WHERE " + 
                   mPrimaryKeyColumnName + "=" + getPrimaryKeyValue(o);

      Log.v(getClass().getSimpleName(), sql);

      db.execSQL(sql);
    }

  } // end of EntityMapping

  private static final HashMap<Class<? extends Entity>, EntityMapping> entityMappings = new HashMap<Class<? extends Entity>, EntityMapping>();

  /*
   * Package private - used by Query as well as locally...
   */
  static EntityMapping getEntityMapping(Class<? extends Entity> clz) {
    EntityMapping mapping = entityMappings.get(clz);

    if (mapping == null) {
      // build map
      entityMappings.put(clz, mapping = EntityMapping.build(clz));
    }

    return mapping;
  }
  
  /*
   * Flushes the schema creation cache, ensuring that all existing mapping's
   * schemas will be recreated if they do not exist when they're next accessed.
   * 
   * See issue #17
   */
  static void flushSchemaCreationCache() {
    for (Class<? extends Entity> clz : entityMappings.keySet()) {
      entityMappings.get(clz).mSchemaCreated = false;
    }
  }

  static EntityMapping getEntityMappingEnsureSchema(SQLiteDatabase db,
      Class<? extends Entity> clz) {
    EntityMapping map = getEntityMapping(clz);
    if (!map.mSchemaCreated) {
      map.createSchema(db);
    }
    return map;
  }

  /**
   * <p>Create a new {@link Query} that will query against the
   * table mapped to the specified class.</p>
   * 
   * <p>See the {@link Query} documentation for examples of
   * usage.</p>
   * 
   * @param clz The class to query.
   * @return A new <code>Query</code>.
   */
  public static <T extends Entity> Query<T> query(Class<T> clz) {
    return new Query<T>(clz);
  }
  
  /**
   * Load the next entity from the given cursor using the default database.
   * 
   * If the cursor does not reference the default database, then you should
   * instead use {@link #load(Class, SQLiteDatabase, Cursor)} to allow the
   * database from which linked entities will be loaded.
   * 
   * @param clz The class of Entity the cursor holds.
   * @param c The cursor.
   * @return the loaded entity.
   */
  public static <T extends Entity> T load(Class<T> clz, Cursor c) {
    return load(clz, ORMDroidApplication.getDefaultDatabase(), c);
  }
  
  /**
   * Load the next entity from the given cursor using the specified database.
   * 
   * @param clz The class of Entity the cursor holds.
   * @param db The database from which to load any linked entities.
   * @param c The cursor.
   * @return the loaded entity.
   */
  public static <T extends Entity> T load(Class<T> clz, SQLiteDatabase db, Cursor c) {
    // Go ahead and assume schema already exists, since we have a cursor...
    return getEntityMapping(clz).<T>load(db, c);
  }

  /**
   * Load all entities from the given cursor using the default database.
   * 
   * If the cursor does not reference the default database, then you should
   * instead use {@link #load(Class, SQLiteDatabase, Cursor)} to allow the
   * database from which linked entities will be loaded.
   * 
   * @param clz The class of Entity the cursor holds.
   * @param c The cursor.
   * @return the loaded entities, in a List.
   */
  public static <T extends Entity> List<T> loadAll(Class<T> clz, Cursor c) {
    return loadAll(clz, ORMDroidApplication.getDefaultDatabase(), c);
  }
  
  /**
   * Load all entities from the given cursor using the specified database.
   * 
   * @param clz The class of Entity the cursor holds.
   * @param db The database from which to load any linked entities.
   * @param c The cursor.
   * @return the loaded entities, in a List.
   */
  public static <T extends Entity> List<T> loadAll(Class<T> clz, SQLiteDatabase db, Cursor c) {
    // Go ahead and assume schema already exists, since we have a cursor...
    return getEntityMapping(clz).<T>loadAll(db, c);
  }

  boolean mTransient;
  private EntityMapping mMappingCache;

  protected Entity() {
    mTransient = true;
  }

  /**
   * <p>Determine whether this instance is backed by the database.</p>
   * 
   * <p><strong>Note</strong> that a <code>false</code> result
   * from this method does <strong>not</strong> indicate that
   * the data in the database is up to date with respect to the
   * object's fields.</p>
   * 
   * @return <code>false</code> if this object is stored in the database.
   */
  public boolean isTransient() {
    return mTransient;
  }

  private EntityMapping getEntityMapping() {
    // This may be called multiple times on a single instance,
    // (e.g. during a save, looking for primary keys and whatnot)
    // so we cache it per instance, to save the hash cache lookup...
    if (mMappingCache != null) {
      return mMappingCache;
    } else {
      return mMappingCache = getEntityMapping(getClass());
    }
  }

  private EntityMapping getEntityMappingEnsureSchema(SQLiteDatabase db) {
    EntityMapping map = getEntityMapping();
    if (!map.mSchemaCreated) {
      map.createSchema(db);
    }
    return map;
  }

  /**
   * <p>Get the value of the primary key field for this object.</p>
   * 
   * <p>Note that this currently uses reflection.</p>
   * 
   * @return The primary key value.
   */
  public Object getPrimaryKeyValue() {
    return getEntityMapping().getPrimaryKeyValue(this);
  }

  /**
   * Insert or update this object using the specified database
   * connection.
   * 
   * @param db The database connection to use.
   * @return The primary key of the inserted item (if object was transient), or -1 if an update was performed.
   */
  public int save(SQLiteDatabase db) {
    EntityMapping mapping = getEntityMappingEnsureSchema(db);

    int result = -1;

    if (mTransient) {
      result = mapping.insert(db, this);
      mTransient = false;
    } else {
      mapping.update(db, this);      
    }

    return result;
  }

  /**
   * Insert or update this object using the default database
   * connection.
   * 
   * @return The primary key of the inserted item (if object was transient), or -1 if an update was performed.
   */
  public int save() {
    SQLiteDatabase db = ORMDroidApplication.getDefaultDatabase();
    db.beginTransaction();

    int result = -1;

    try {
      result = save(db);
      db.setTransactionSuccessful();
    } finally {
      db.endTransaction();
    }

    db.close();
    return result;
  }
  
  /**
   * Delete this object using the specified database connection.
   * 
   * @param db The database connection to use.
   */
  public void delete(SQLiteDatabase db) {
    EntityMapping mapping = getEntityMappingEnsureSchema(db);

    if (!mTransient) {
      mapping.delete(db, this);
      this.mTransient = true;
    }
  }
  
  /**
   * Delete this object using the default database connection.
   */
  public void delete() {
    if (!mTransient) {
      SQLiteDatabase db = ORMDroidApplication.getDefaultDatabase();
      db.beginTransaction();
  
      try {
        delete(db);
        db.setTransactionSuccessful();
      } finally {
        db.endTransaction();
      }
  
      db.close();
    }
  }
  
  /**
   * Defines equality in terms of primary key values. 
   */
  @Override
  public boolean equals(Object other) {
    // TODO indirectly using reflection here (via getPrimaryKeyValue).
    return other != null && 
           other.getClass().equals(getClass()) && 
           ((Entity) other).getPrimaryKeyValue().equals(getPrimaryKeyValue());    
  }

  /**
   * Defines the hash code in terms of the primary key value. 
   */
  @Override
  public int hashCode() {
    // TODO this uses reflection. Also, could act wierd if non-int primary keys... 
    return 31 * getClass().hashCode() + getPrimaryKeyValue().hashCode();
  }
}




Java Source Code List

com.roscopeco.ormdroid.Column.java
com.roscopeco.ormdroid.DateTypeMapping.java
com.roscopeco.ormdroid.EntityTypeMapping.java
com.roscopeco.ormdroid.Entity.java
com.roscopeco.ormdroid.ListTypeMapping.java
com.roscopeco.ormdroid.MappingList.java
com.roscopeco.ormdroid.NumericTypeMapping.java
com.roscopeco.ormdroid.ORMDroidApplication.java
com.roscopeco.ormdroid.ORMDroidException.java
com.roscopeco.ormdroid.Query.java
com.roscopeco.ormdroid.StringTypeMapping.java
com.roscopeco.ormdroid.Table.java
com.roscopeco.ormdroid.TypeMapper.java
com.roscopeco.ormdroid.TypeMappingException.java
com.roscopeco.ormdroid.TypeMapping.java