/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* 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 org.jumpmind.symmetric.android;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.NotImplementedException;
import org.jumpmind.db.model.TypeMap;
import org.jumpmind.db.sql.AbstractSqlTemplate;
import org.jumpmind.db.sql.ISqlReadCursor;
import org.jumpmind.db.sql.ISqlResultsListener;
import org.jumpmind.db.sql.ISqlRowMapper;
import org.jumpmind.db.sql.ISqlStatementSource;
import org.jumpmind.db.sql.ISqlTransaction;
import org.jumpmind.db.sql.ListSqlStatementSource;
import org.jumpmind.db.sql.Row;
import org.jumpmind.db.sql.UniqueKeyException;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class AndroidSqlTemplate extends AbstractSqlTemplate {
protected SQLiteOpenHelper databaseHelper;
protected Context androidContext;
public AndroidSqlTemplate(SQLiteOpenHelper databaseHelper, Context androidContext) {
this.databaseHelper = databaseHelper;
this.androidContext = androidContext;
}
public SQLiteOpenHelper getDatabaseHelper() {
return databaseHelper;
}
public byte[] queryForBlob(String sql, int jdbcTypeCode, String jdbcTypeName, Object... params) {
return queryForBlob(sql, params);
}
public byte[] queryForBlob(String sql, Object... params) {
return queryForObject(sql, byte[].class, params);
}
@Override
public String queryForClob(String sql, Object... args) {
return queryForClob(sql, Types.CLOB, TypeMap.CLOB, args);
}
public String queryForClob(String sql, int jdbcTypeCode, String jdbcTypeName, Object... params) {
return queryForString(sql, params);
}
public <T> T queryForObject(String sql, Class<T> clazz, Object... params) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
try {
return queryForObject(database, sql, clazz, params);
} catch (Exception ex) {
throw translate(ex);
} finally {
close(database);
}
}
protected <T> T queryForObject(SQLiteDatabase database, String sql, Class<T> clazz,
Object... params) {
Cursor cursor = null;
try {
T result = null;
cursor = database.rawQuery(sql, toStringArray(params));
if (cursor.moveToFirst()) {
result = get(cursor, clazz, 0);
}
return result;
} catch (Exception ex) {
throw translate(ex);
} finally {
close(cursor);
}
}
public Map<String, Object> queryForMap(final String sql, final Object... args) {
return queryForObject(sql, new ISqlRowMapper<Map<String, Object>>() {
public Map<String, Object> mapRow(Row rs) {
return rs;
}
}, args);
}
public <T> ISqlReadCursor<T> queryForCursor(String sql, ISqlRowMapper<T> mapper,
Object[] params, int[] types) {
return new AndroidSqlReadCursor<T>(sql, toStringArray(params), mapper, this);
}
public int update(boolean autoCommit, boolean failOnError, int commitRate,
ISqlResultsListener resultsListener, String... sql) {
return this.update(autoCommit, failOnError, true, true,
commitRate, resultsListener, new ListSqlStatementSource(sql));
}
public int update(final boolean autoCommit, final boolean failOnError, boolean failOnDrops,
boolean failOnSequenceCreate, final int commitRate, final ISqlResultsListener resultsListener, final ISqlStatementSource source) {
int row = 0;
SQLiteDatabase database = this.databaseHelper.getWritableDatabase();
String currentStatement = null;
try {
if (!autoCommit) {
database.beginTransaction();
}
for (String statement = source.readSqlStatement(); statement != null; statement = source
.readSqlStatement()) {
currentStatement = statement;
update(statement);
row++;
if (!autoCommit && row % commitRate == 0) {
database.setTransactionSuccessful();
database.endTransaction();
database.beginTransaction();
}
if (resultsListener != null) {
resultsListener.sqlApplied(statement, row, 0, row);
}
}
if (!autoCommit) {
database.setTransactionSuccessful();
}
} catch (RuntimeException ex) {
if (resultsListener != null) {
resultsListener.sqlErrored(currentStatement, translate(currentStatement, ex), row, false, false);
}
throw ex;
} finally {
if (!autoCommit) {
database.endTransaction();
}
close(database);
}
return row;
}
public int update(boolean autoCommit, boolean failOnError, int commitRate, String... sql) {
return update(autoCommit, failOnError, commitRate, (ISqlResultsListener) null, sql);
}
public int update(String sql, Object[] values, int[] types) {
SQLiteDatabase database = this.databaseHelper.getWritableDatabase();
try {
return update(database, sql, values, types);
} finally {
close(database);
}
}
protected int update(SQLiteDatabase database, String sql, Object[] values, int[] types) {
try {
if (values != null) {
database.execSQL(sql, toStringArray(values));
} else {
database.execSQL(sql);
}
return queryForObject(database, "select changes()", Integer.class);
} catch (Exception ex) {
throw translate(ex);
}
}
/**
* Translate an array of {@link Object} to an array of {@link String} by
* creating a new array of {@link String} and putting each of the objects
* into the array by calling {@link Object#toString()}
*
* @param orig
* the original array
* @return a newly constructed string array
*/
public static String[] toStringArray(Object[] orig) {
String[] array = null;
if (orig != null) {
array = new String[orig.length];
for (int i = 0; i < orig.length; i++) {
if (orig[i] != null) {
if (orig[i] instanceof Date) {
array[i] = new Timestamp(((Date) orig[i]).getTime()).toString();
} else {
array[i] = orig[i].toString();
}
}
}
}
return array;
}
public void testConnection() {
SQLiteDatabase database = this.databaseHelper.getWritableDatabase();
close(database);
}
public boolean isUniqueKeyViolation(Throwable ex) {
return ex instanceof SQLiteConstraintException || ex instanceof UniqueKeyException;
}
public boolean isForeignKeyViolation(Throwable ex) {
return false;
}
@Override
public ISqlTransaction startSqlTransaction(boolean autoCommit) {
return new AndroidSqlTransaction(this, autoCommit);
}
public ISqlTransaction startSqlTransaction() {
return startSqlTransaction(false);
}
public int getDatabaseMajorVersion() {
SQLiteDatabase database = this.databaseHelper.getWritableDatabase();
try {
return database.getVersion();
} catch (Exception ex) {
throw translate(ex);
} finally {
close(database);
}
}
public int getDatabaseMinorVersion() {
return 0;
}
public String getDatabaseProductName() {
return "sqlite";
}
public String getDatabaseProductVersion() {
return Integer.toString(getDatabaseMajorVersion());
}
public String getDriverName() {
return "android";
}
public String getDriverVersion() {
try {
return androidContext.getPackageManager().getPackageInfo(
androidContext.getPackageName(), 0).versionName;
} catch (NameNotFoundException e) {
return "?";
}
}
public Set<String> getSqlKeywords() {
throw new NotImplementedException();
}
public boolean supportsGetGeneratedKeys() {
return false;
}
public boolean isStoresUpperCaseIdentifiers() {
return false;
}
public boolean isStoresLowerCaseIdentifiers() {
return true;
}
public boolean isStoresMixedCaseQuotedIdentifiers() {
return false;
}
public long insertWithGeneratedKey(String sql, String column, String sequenceName,
Object[] params, int[] types) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
try {
return insertWithGeneratedKey(database, sql, column, sequenceName, params, types);
} finally {
close(database);
}
}
protected long insertWithGeneratedKey(SQLiteDatabase database, String sql, String column,
String sequenceName, Object[] params, int[] types) {
int updateCount = update(database, sql, params, types);
if (updateCount > 0) {
long rowId = queryForObject(database, "SELECT last_insert_rowid()", Integer.class);
return rowId;
} else {
return -1;
}
}
protected void close(SQLiteDatabase database) {
}
protected void close(Cursor cursor) {
try {
if (cursor != null) {
cursor.close();
}
} catch (Exception ex) {
}
}
@SuppressWarnings("unchecked")
protected <T> T get(Cursor cursor, Class<T> clazz, int columnIndex) {
Object result = null;
if (clazz.equals(String.class)) {
result = (String) cursor.getString(columnIndex);
} else if (clazz.equals(Integer.class)) {
result = (Integer) cursor.getInt(columnIndex);
} else if (clazz.equals(Integer.class)) {
result = (Double) cursor.getDouble(columnIndex);
} else if (clazz.equals(Float.class)) {
result = (Float) cursor.getFloat(columnIndex);
} else if (clazz.equals(Long.class)) {
result = (Long) cursor.getLong(columnIndex);
} else if (clazz.equals(Date.class) || clazz.equals(Timestamp.class)) {
String dateString = cursor.getString(columnIndex);
if (dateString.contains("-")) {
result = Timestamp.valueOf(dateString);
} else {
result = new Timestamp(Long.parseLong(dateString));
}
} else if (clazz.equals(Short.class)) {
result = (Short) cursor.getShort(columnIndex);
} else if (clazz.equals(byte[].class)) {
result = (byte[]) cursor.getBlob(columnIndex);
} else {
throw new IllegalArgumentException("Unsupported class: " + clazz.getName());
}
return (T) result;
}
}