/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2011 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package com.funambol.storage;
import java.util.Enumeration;
import java.util.Vector;
import java.io.IOException;
/**
* This abstract class defines a generic data store for values orgnized
* in a table.
* The store is persistable and each concrete implementation is free to choose where and
* how data is persisted.
* Each table row is a tuple of fields, each one having its own type. Possible
* types are:
*
* 1) STRING
* 2) LONG
*
* Following some examples on how to use the table.
*
* Example 1
*
* In this example a table with 2 fruits is setup. Each fruit is described by
* two values: its color and the number of days it lasts before rottening. The
* key is the the fruit name and it is stored in the first field.
*
* String colsType = { Table.TYPE_STRING, Table.TYPE_STRING, Table.TYPE_LONG };
* Table table = new Table("fruits", colsType, 0);
* table.open(true);
*
* StringTuple bananRow = table.createNewRow("banana");
* bananaRow.setField(1,"yellow");
* bananaRow.setField(2,5);
* table.insert(bananaRow);
*
* StringTuple orangeRow = table.createNewRow("orange");
* orangeRow.setField(1,"orange");
* orangeRow.setField(2,3);
* table.insert(orangeRow);
*
* table.save();
* table.close();
*
*
* Example 2
*
* Given the table created in example 1, we now query and print its content
*
* String colsType = { Table.TYPE_STRING, Table.TYPE_STRING, Table.TYPE_LONG };
* StringTable table = new StringTable("fruits", colsType, 0);
*
* if (table.exists()) {
* table.open(false);
* QueryResult values = table.query();
* while(rows.hasMoreElements()) {
* Tuple value = (Tuple)rows.nextElement();
* String fruit = (String)value.getKey();
* String color = value.getStringField(1);
* int days = (int)value.getLongField(2);
*
* System.out.println("Fruit " + fruit + " is " + color + " and last " + days + " days.");
* }
* res.close();
* table.close();
* } else {
* System.err.println("Fruits table not found");
* }
*
* Example 3
*
* Given the table created in example 1, we now query searching for the banana fruit
*
* StringTable table = new StringTable("fruits");
* String colsType = { Table.TYPE_STRING, Table.TYPE_STRING, Table.TYPE_LONG };
*
* if (table.exists()) {
* table.open(false);
* QueryFilter filter = table.createQueryFilter("banana");
* QueryResult res = table.query(filter);
* if (res.hasMoreElements()) {
* Tuple value = (Tuple)res.nextElement();
* String color = value.getStringField(1);
* int days = (int)value.getLongField(2);
* System.out.println("Banana is " + color + " and last " + days + " days.");
* }
* res.close();
* } else {
* System.err.println("Fruits table not found");
* }
*
* Example 4
*
* Given the table created in example 1, we now query searching for the fruits of a given color
*
* StringTable table = new StringTable("fruits");
* if (table.exists()) {
* table.open(false);
* QueryFilter filter = table.createQueryFilter();
* filter.setValueFilter(1, QueryFilter.EQUAL, "yellow");
* QueryResult res = table.query(filter);
* while(res.hasMoreElements()) {
* Tuple value = (Tuple)res.nextElement();
* String key = value.getKey();
* System.out.println("Found a yellow fruit " + key);
* }
* res.close();
* } else {
* System.err.println("Fruits table not found");
* }
*
*/
public abstract class Table {
private static final String TAG_LOG = "Table";
public static final int TYPE_STRING = 0;
public static final int TYPE_LONG = 1;
private static final String KEY_COLUMN_NAME = "key";
private static final String VALUE_COLUMN_NAME_PREFIX = "value";
private String name;
private int arity;
private int keyIdx;
private int colsType[];
private String colsName[];
protected boolean autoincrement;
private Vector observers = new Vector();
public Table(String name, int colsType[], int keyIdx, boolean autoincrement) {
this.name = name;
this.colsType = colsType;
this.autoincrement = autoincrement;
this.keyIdx = keyIdx;
this.arity = colsType.length;
}
public Table(String name, String colsName[], int colsType[], int keyIdx, boolean autoincrement) {
this(name, colsType, keyIdx, autoincrement);
this.colsName = colsName;
}
public Table(String name, int colsType[], int keyIdx) {
this(name, colsType, keyIdx, false);
}
public Tuple createNewRow(String key) {
Tuple tuple = new Tuple(colsType, getKeyIdx(), this);
tuple.setField(getKeyIdx(), key);
return tuple;
}
public Tuple createNewRow(Long key) {
Tuple tuple = new Tuple(colsType, getKeyIdx(), this);
tuple.setField(getKeyIdx(), key);
return tuple;
}
public Tuple createNewRow(long key) {
Tuple tuple = new Tuple(colsType, getKeyIdx(), this);
tuple.setField(getKeyIdx(), new Long(key));
return tuple;
}
public Tuple createNewRow() {
Tuple tuple = new Tuple(colsType, getKeyIdx(), this);
return tuple;
}
public QueryFilter createQueryFilter() {
QueryFilter filter = new QueryFilter();
return filter;
}
public QueryFilter createQueryFilter(Object key) {
QueryFilter filter = new QueryFilter(key);
return filter;
}
public QueryFilter createQueryFilter(Vector keys) {
QueryFilter filter = new QueryFilter(keys);
return filter;
}
public String getName() {
return name;
}
public int getArity() {
return arity;
}
public int getColType(int idx) {
if (idx >= arity) {
throw new IllegalArgumentException("Invalid index");
}
return colsType[idx];
}
public String getColName(int i) {
if (colsName == null) {
if (i == keyIdx) {
return KEY_COLUMN_NAME;
}
StringBuffer colName = new StringBuffer(VALUE_COLUMN_NAME_PREFIX).append("_").append(i);
return colName.toString();
} else {
return colsName[i];
}
}
public int[] getColsType() {
return colsType;
}
public int getKeyIdx() {
return keyIdx;
}
public int getColIndexOrThrow(String colName) {
for(int i=0;i<arity;++i) {
if (colName.equals(getColName(i))) {
return i;
}
}
throw new IllegalArgumentException("Cannot find column named " + colName);
}
/**
* Open the table. Once a table is open, it is possible to perform
* operations. Usually the first operation to be performed is "loading" the
* table content.
*
* @throws IOException if the operation fails. For example if the table does
* not exist and the user did not specify the createIfNotExist flag
*/
public abstract void open() throws IOException;
/**
* Close the current table. This will release all the resources associated
* to this resource and no other operation can be performed.
*/
public abstract void close() throws IOException;
/**
* Insert a new item into the table. There is no guarantee the item is persisted
* until the table is saved or closed.
* null values in the tuple are NOT allowed.
* The tuple cannot contain unchanged values as they do not make sense while
* inserting new items.
*
* @param tuple the value for this item
*
* @throws IllegalArgumentException if at least one value is null, or the key is not unique
* @throws IOException if the operation cannot be perfomed on the storage
*/
public void insert(Tuple tuple) throws IOException {
insertTuple(tuple);
// Notify all the observers that a new tuple has been insterted
for(int i=0;i<observers.size();++i) {
TableObserver observer = (TableObserver)observers.elementAt(i);
observer.tupleInserted(tuple);
}
}
protected abstract void insertTuple(Tuple tuple) throws IOException;
/**
* Update an existing item into the table. There is no guarantee the item is persisted
* until the table is saved or closed.
* If a value needs to be left unchanged, then the corresponding value in
* the tuple shall be "unchanged".
* null values in the tuple are NOT allowed.
*
* @param key the unique key of the existing item
* @param value the value to be stored
* @throws IllegalArgumentException if at least one value is null or the value to be updated does not exist
* @throws IOException if the operation cannot be perfomed on the storage
*/
public void update(Tuple tuple) throws IOException {
updateTuple(tuple);
// Notify all the observers that a tuple has been updated
for(int i=0;i<observers.size();++i) {
TableObserver observer = (TableObserver)observers.elementAt(i);
observer.tupleUpdated(tuple);
}
}
protected abstract void updateTuple(Tuple tuple) throws IOException;
/**
* Removes an entry from the table
*
* @param key the item key
* @throws IOException if the operation cannot be perfomed on the storage
*/
public void delete(Object key) throws IOException {
deleteTuple(key);
// Notify all the observers that the table got reset
for(int i=0;i<observers.size();++i) {
TableObserver observer = (TableObserver)observers.elementAt(i);
observer.tupleDeleted(key);
}
}
protected abstract void deleteTuple(Object key) throws IOException;
/**
* Returns an enumeration with all the tuples in the store. The first column
* in the returned tuples is always the key.
*
* The elements type is <code>StringTuple</code>.
*
* @throws IOException if the operation cannot be perfomed on the storage
*/
public QueryResult query() throws IOException {
return query(null, -1, false);
}
public QueryResult query(QueryFilter filter) throws IOException {
return query(filter, -1, false);
}
public abstract QueryResult query(QueryFilter filter, int orderBy, boolean ascending) throws IOException;
/**
* Returns true iff key is contained in this store.
*/
public boolean contains(Object key) throws IOException {
QueryFilter filter = new QueryFilter(key);
QueryResult res = query(filter);
boolean r = res.hasMoreElements();
res.close();
return r;
}
/**
* Persist this store.
*
* @throws IOException if the operation cannot be performed
*/
public abstract void save() throws IOException;
/**
* Resets this data store. All data is lost after this call. The basic
* implementation removes all rows, one by one. Concrete implementations
* should override this implementation to be more efficient.
*
* @throws IOException if the operation fails
*/
public void reset() throws IOException {
resetTable();
// Notify all the observers that the table got reset
for(int i=0;i<observers.size();++i) {
TableObserver observer = (TableObserver)observers.elementAt(i);
observer.tableReset();
}
}
public void registerObserver(TableObserver observer) {
if (!observers.contains(observer)) {
observers.addElement(observer);
}
}
public void unregisterObserver(TableObserver observer) {
if (observers.contains(observer)) {
observers.removeElement(observer);
}
}
protected void resetTable() throws IOException {
QueryResult rows = query();
try {
while(rows.hasMoreElements()) {
Tuple tuple = (Tuple)rows.nextElement();
Object key = tuple.getKey();
delete(key);
}
} finally {
rows.close();
}
}
public void drop() throws IOException {
dropTable();
// Notify all the observers that the table got reset
for(int i=0;i<observers.size();++i) {
TableObserver observer = (TableObserver)observers.elementAt(i);
observer.tableDropped();
}
}
protected abstract void dropTable() throws IOException;
}