Android Open Source - concrete Record






From Project

Back to project page concrete.

License

The source code is released under:

MIT License

If you think the Android project concrete 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 (c) 2010 Peter Haldbk, peter.haldbaek@gmail.com
 * //from   w w  w . ja va2s . c o  m
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *  
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
package com.briskbee.concrete;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;

/**
 * <p>
 * The Record class represents a record in the database. It contains methods
 * for basic CRUD operations as well as methods for querying the database
 * for records. Use this class by creating a subclass with public variables:
 * </p>
 * 
 * <pre>
 * public class Foo extends Record<Foo> {
 *   public int i;
 *   public String s;
 *   public Date d;
 *   
 *   public Foo(ConcreteContext context) {
 *     super(context);
 *   }
 * }
 * </pre>
 * 
 * <p>
 * This represents a record in a table called <code>foo</code>. The table
 * contains the elements i of type <code>integer</code>, s of type
 * <code>text</code> and d of type <code>integer</code> (dates are modelled
 * as integers).
 * </p>
 * 
 * @author Peter Haldbk, peter.haldbaek@gmail.com
 */
public abstract class Record<T extends Record<?>> {
  private String DELIMITER = "_";
  
  public long _id = -1;
  
  private ConcreteContext context;
  
  private String TABLE;
  private String fieldName; // The name of the field this class is in the declaring class
  
  private Map<String, Field> fields;
  private Map<String, Record<T>> relations; // <Field name, Record>
  
  public Record(ConcreteContext context) {
    this.context = context;
  }
  
  /**
   * Loads the record with the specified id.
   * @param id id of the record to load
   */
  public void load(long id) {
    this._id = id;
    
    SQLiteDatabase db = context.getDbHelper().getReadableDatabase();
    Cursor cursor = null;
    try {
      // Create sql
      String[] selectionArgs = new String[]{ String.valueOf(_id) };
      String sql = createSelectSql(selectionArgs);
      
      // Execute query
      cursor = db.rawQuery(sql, selectionArgs);
      
      // Extract data
      cursor.moveToFirst();
      while (!cursor.isAfterLast()) {
        for (int i = 0; i < cursor.getColumnCount(); i++) {
          String columnName = cursor.getColumnName(i);
          setValue(this, columnName, cursor, i);
        }
        
        cursor.moveToNext();
      }
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    } finally {
      if (cursor != null)
        cursor.close();
      if (db != null && db.isOpen())
        db.close();
    }
  }
  
  /* package */ String createSelectSql(String[] selectionArgs) {
    StringBuffer sql = new StringBuffer("SELECT ");
    
    // Add fields
    getSelectList(sql, "", true);
    
    sql.append(" FROM ");
    sql.append(getTable());
    for (Record<T> relation : getRelations().values())
      sql.append(", " + relation.getTable());
    if ((selectionArgs != null && selectionArgs.length > 0) || !getRelations().isEmpty()) {
      sql.append(" WHERE ");
      // inner join
      // TODO Add support for more than one level of relations
      for (Record<T> relation : getRelations().values()) {
        sql.append(getTable());
        sql.append(".");
        sql.append(relation.getTable());
        sql.append("_id=");
        sql.append(relation.getTable());
        sql.append("._id AND ");
      }
      sql.append(getTable());
      sql.append("._id = ?");
    }
    
    return sql.toString();
  }
  
  private void setValue(Record<T> record, String columnName, Cursor cursor, int column) {
    Field field = record.getFields().get(columnName);
    if (field != null) {
      // Simple field
      Object value = ReflectionUtil.getValue(field, cursor, column);
      setValue(record, field, value);
    } else {
      String f = columnName.substring(0, columnName.indexOf(DELIMITER));
      Record<T> relation = record.getRelations().get(f);
      if (relation != null) {
        // Relation
        columnName = columnName.substring(columnName.indexOf(DELIMITER)+1);
        setValue(relation, columnName, cursor, column);
      }
    }
  }
  
  private String getSelectList(StringBuffer selectList, String prefix, boolean first) {
    // Simple fields first
    for (Field field : getFields().values()) {
      appendField(selectList, getTable(), prefix, field, first);
      first = false;
    }
    
    // Then relations recursively
    for (String fieldName : getRelations().keySet()) {
      Record<T> relation = getRelations().get(fieldName);
      relation.getSelectList(selectList, isEmpty(prefix) ? relation.fieldName : prefix + DELIMITER + relation.fieldName, first);
    }
    
    return selectList.toString();
  }
  
  private void setValue(Object receiver, Field field, Object value) {
    try {
      field.set(receiver, value);
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    }
  }
  
  private void appendField(StringBuffer buffer, String table, String prefix, Field field, boolean first) {
    if (!first)
      buffer.append(", ");
    buffer.append(table);
    buffer.append(".");
    buffer.append(field.getName());
    buffer.append(" AS ");
    if (!isEmpty(prefix)) {
      buffer.append(prefix);
      buffer.append(DELIMITER);
    }
    buffer.append(field.getName());
  }
  
  /**
   * Saves the record in the database.
   */
  public void save() {
    SQLiteDatabase db = context.getDbHelper().getWritableDatabase();
    try {
      ContentValues values = getContentValues();
      _id = db.insert(getTable(), null, values);
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    } finally {
      if (db != null && db.isOpen())
        db.close();
    }
  }
  
  /**
   * Updates the record in the database.
   */
  public void update() {
    SQLiteDatabase db = context.getDbHelper().getWritableDatabase();
    try {
      ContentValues values = getContentValues();
      String whereClause = BaseColumns._ID + " = ?";
      String[] whereArgs = new String[]{ String.valueOf(_id) };
      db.update(getTable(), values, whereClause, whereArgs);
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    } finally {
      if (db != null && db.isOpen())
        db.close();
    }
  }
  
  /**
   * Either saves or updates the record in the database. If the
   * record has it id field set, the record is updated; otherwise
   * a new record is created in the database.
   */
  public void saveOrUpdate() {
    if (_id > -1)
      update();
    else
      save();
  }
  
  /**
   * Deletes the record in the database (without cascade).
   */
  public void delete() {
    SQLiteDatabase db = context.getDbHelper().getWritableDatabase();
    try {
      String[] whereArgs = new String[]{ String.valueOf(_id) };
      db.delete(getTable(), BaseColumns._ID + " = ?", whereArgs);
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    } finally {
      if (db != null && db.isOpen())
        db.close();
    }
  }
  
  /**
   * Returns the first record found by the specified query.
   * 
   * @param selection the selection query
   * @param selectionArgs the arguments for the selection query
   * @param orderBy the order of the records
   * @return the first record found by the specified query
   */
  public T first(String selection, String[] selectionArgs, String orderBy) {
    return find(selection, selectionArgs, null, null, orderBy, "1").get(0);
  }
  
  /**
   * Returns all records for the class.
   * 
   * @return all records for the class
   */
  public List<T> find() {
    return find(null, null, null, null, null, null);
  }
  
  /**
   * Returns the records matching the specified search criteria.
   * 
   * @param selection the selection query
   * @param selectionArgs the arguments for the selection query
   * @param groupBy the group by clause
   * @param having the having clause
   * @param orderBy the order of the records
   * @param limit the limit clause
   * @return the records matching the specified search criteria
   */
  public List<T> find(String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
    SQLiteDatabase db = context.getDbHelper().getReadableDatabase();
    Cursor cursor = null;
    try {
      List<T> result = new ArrayList<T>();
      
      // Create sql
      String sql = createSelectSql(selectionArgs);
      
      // Execute query
      cursor = db.rawQuery(sql, selectionArgs);
      
      // Extract data
      cursor.moveToFirst();
      while (!cursor.isAfterLast()) {
        Constructor<? extends Record> constructor = getClass().getConstructor(new Class[]{ ConcreteContext.class });
        Record<T> o = (Record<T>)constructor.newInstance(context);
        
        for (int i = 0; i < cursor.getColumnCount(); i++) {
          String columnName = cursor.getColumnName(i);
          setValue(o, columnName, cursor, i);
        }
        
        result.add((T)o);
        cursor.moveToNext();
      }
      
      return result;
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    } finally {
      if (cursor != null)
        cursor.close();
      if (db != null && db.isOpen())
        db.close();
    }
  }
  
  /**
   * Converts a list of records to a map which can be used by the <code>RecordAdapter</code>
   * when populating activities (views).
   * 
   * @param records the list of records
   * @return a map compatible with the <code>RecordAdapter</code>
   * @see RecordAdapter
   */
  public static List<? extends Map<String, ?>> toMap(List<? extends Record<?>> records) {
    if (records == null)
      return null;
    
    List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
    for (Record<?> record : records) {
      // Create new item
      Map<String, Object> item = new TreeMap<String, Object>();
      list.add(item);
      
      // Loop through all fields
      for (Field field : record.getClass().getFields()) {
        try {
          item.put(field.getName(), field.get(record));
        } catch (Exception e) {
          Session.getContext().getLog().e(Record.class.getSimpleName(), e.getMessage(), e);
          throw new ConcreteException(e);
        }
      }
      
      // Loop through all methods
      for (Method method : record.getClass().getDeclaredMethods()) {
        try {
          if (method.getParameterTypes().length == 0 && method.getName().startsWith("get")) {
            item.put(method.getName().substring(3).toLowerCase(), method.invoke(record).toString());
          }
        } catch (Exception e) {
          Session.getContext().getLog().e(Record.class.getSimpleName(), e.getMessage(), e);
          throw new ConcreteException(e);
        }
      }
    }
    
    return list;
  }
  
  /**
   * Deletes all records of the class which fulfills the specified condition.
   * 
   * @param condition the condition
   * @param arguments the arguments used in the condition
   */
  public void deleteAll(String condition, String... arguments) {
    SQLiteDatabase db = null;
    try {
      db = context.getDbHelper().getWritableDatabase();
      db.delete(getTable(), condition, arguments);
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    } finally {
      if (db != null && db.isOpen())
        db.close();
    }
  }
  
  /**
   * Returns the name of the underlying database table used by this class.
   * 
   * @return the name of the underlying database table
   */
  protected String getTable() {
    if (TABLE == null)
      TABLE = getClass().getSimpleName();
    
    return TABLE;
  }
  
  private ContentValues getContentValues() {
    ContentValues values = new ContentValues();
    for (Field field : getFields().values()) {
      try {
        String name = field.getName();
        if (!BaseColumns._ID.equals(name)) {
          String type = field.getType().getName();
          if ("java.lang.String".equals(type)) {
            values.put(name, (String)field.get(this));
          } else if ("long".equals(type)) {
            values.put(name, (Long)field.get(this));
          } else if ("double".equals(type)) {
            values.put(name, (Double)field.get(this));
          } else if ("float".equals(type)) {
            values.put(name, (Float)field.get(this));
          } else if ("int".equals(type)) {
            values.put(name, (Integer)field.get(this));
          } else if ("short".equals(type)) {
            values.put(name, (Short)field.get(this));
          } else if ("java.util.Date".equals(type)) {
            Date d = (Date)field.get(this);
            values.put(name, d.getTime());
          }
        }
      } catch (Exception e) {
        context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
        throw new ConcreteException(e);
      }
    }
    
    return values;
  }
  
  private Map<String, Field> getFields() {
    if (fields == null) {
      initializeFields();
    }
    
    return fields;
  }
  
  private Map<String, Record<T>> getRelations() {
    if (relations == null) {
      initializeFields();
    }
    
    return relations;
  }
  
  @SuppressWarnings("unchecked")
  private void initializeFields() {
    fields = new TreeMap<String, Field>();
    relations = new TreeMap<String, Record<T>>();
    try {
      for (Field f : getClass().getFields()) {
        Object o = f.get(this);
        if (ReflectionUtil.isRelation(o, f)) {
          ((Record<T>)o).fieldName = f.getName();
          relations.put(((Record<T>)o).fieldName, (Record<T>)o);
        }
        else
          fields.put(f.getName(), f);
      }
    } catch (Exception e) {
      context.getLog().e(this.getClass().getSimpleName(), e.getMessage(), e);
      throw new ConcreteException(e);
    }
  }
  
  private boolean isEmpty(String s) {
    return s == null || s.length() == 0;
  }
}




Java Source Code List

com.briskbee.concrete.ConcreteContext.java
com.briskbee.concrete.ConcreteException.java
com.briskbee.concrete.RecordAdapter.java
com.briskbee.concrete.Record.java
com.briskbee.concrete.ReflectionUtil.java
com.briskbee.concrete.Session.java
com.briskbee.concrete.db.ConcreteDbHelper.java
com.briskbee.concrete.example.FooActivity.java
com.briskbee.concrete.example.FooListActitivy.java
com.briskbee.concrete.example.context.ExampleContext.java
com.briskbee.concrete.example.db.DbHelper.java
com.briskbee.concrete.example.record.Foo.java
com.briskbee.concrete.log.AndroidLog.java
com.briskbee.concrete.log.ConsoleLog.java
com.briskbee.concrete.log.Log.java