Skip to content
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apply plugin: 'java'
apply plugin: 'maven-publish'
apply from: 'gradle-mvn-push.gradle'

targetCompatibility = '1.6'
Expand Down
11 changes: 10 additions & 1 deletion src/com/activeandroid/ActiveAndroid.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import android.database.sqlite.SQLiteDatabase;

import com.activeandroid.util.Log;
import android.os.Build;

public final class ActiveAndroid {
//////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -60,8 +61,16 @@ public static SQLiteDatabase getDatabase() {
return Cache.openDatabase();
}

/**
* Non-exclusive transactions allows BEGIN IMMEDIATE
* blocks, allowing better read concurrency.
*/
public static void beginTransaction() {
Cache.openDatabase().beginTransaction();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
Cache.openDatabase().beginTransaction();
} else {
Cache.openDatabase().beginTransactionNonExclusive();
}
}

public static void endTransaction() {
Expand Down
20 changes: 20 additions & 0 deletions src/com/activeandroid/DatabaseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.activeandroid.util.NaturalOrderComparator;
import com.activeandroid.util.SQLiteUtils;
import com.activeandroid.util.SqlParser;
import android.os.Build;

public final class DatabaseHelper extends SQLiteOpenHelper {
//////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -66,6 +67,25 @@ public DatabaseHelper(Configuration configuration) {
// OVERRIDEN METHODS
//////////////////////////////////////////////////////////////////////////////////////

/**
* onConfigure is called when the db connection
* is being configured. It's the right place
* to enable write-ahead logging or foreign
* key support.
*
* Available for API level 16 (JellyBean) and above.
*/
@Override
public void onConfigure(SQLiteDatabase db) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
db.enableWriteAheadLogging();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
db.setForeignKeyConstraintsEnabled(true);
}
executePragmas(db);
}

@Override
public void onOpen(SQLiteDatabase db) {
executePragmas(db);
Expand Down
4 changes: 0 additions & 4 deletions src/com/activeandroid/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,6 @@ else if (ReflectionUtils.isSubclassOf(fieldType, Enum.class)) {
Log.e(e.getClass().getName(), e);
}
}

if (mId != null) {
Cache.addEntity(this);
}
}


Expand Down
84 changes: 68 additions & 16 deletions src/com/activeandroid/ModelInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.Map;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;

import com.activeandroid.serializer.CalendarSerializer;
import com.activeandroid.serializer.FileSerializer;
Expand All @@ -41,6 +43,12 @@
import dalvik.system.DexFile;

final class ModelInfo {
private static final String PREFS_FILE = "multidex.version";
private static final String SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String KEY_DEX_NUMBER = "dex.number";
private static final String EXTRACTED_SUFFIX = ".zip";

//////////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
//////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -125,28 +133,41 @@ private boolean loadModelFromMetaData(Configuration configuration) {

private void scanForModel(Context context) throws IOException {
String packageName = context.getPackageName();
String sourcePath = context.getApplicationInfo().sourceDir;
List<String> paths = new ArrayList<String>();

if (sourcePath != null && !(new File(sourcePath).isDirectory())) {
DexFile dexfile = new DexFile(sourcePath);
Enumeration<String> entries = dexfile.entries();
try {
for (String sourcePath : getSourcePaths(context)) {
try {
if (sourcePath != null && !(new File(sourcePath).isDirectory())) {
DexFile dexfile;
if (sourcePath.endsWith(EXTRACTED_SUFFIX))
dexfile = DexFile.loadDex(sourcePath, sourcePath + ".tmp", 0);
else
dexfile = new DexFile(sourcePath);
Enumeration<String> entries = dexfile.entries();

while (entries.hasMoreElements()) {
paths.add(entries.nextElement());
}
}
// Robolectric fallback
else {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources("");
while (entries.hasMoreElements()) {
paths.add(entries.nextElement());
}
}
// Robolectric fallback
else {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources("");

while (resources.hasMoreElements()) {
String path = resources.nextElement().getFile();
if (path.contains("bin") || path.contains("classes")) {
paths.add(path);
while (resources.hasMoreElements()) {
String path = resources.nextElement().getFile();
if (path.contains("bin") || path.contains("classes")) {
paths.add(path);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}

for (String path : paths) {
Expand All @@ -155,6 +176,34 @@ private void scanForModel(Context context) throws IOException {
}
}

private static SharedPreferences getMultiDexPreferences(Context context) {
int mode = Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS;
return context.getSharedPreferences(PREFS_FILE, mode);
}

private static List<String> getSourcePaths(Context context) throws Exception {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
File sourceApk = new File(applicationInfo.sourceDir);
File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

List<String> sourcePaths = new ArrayList<String>();
sourcePaths.add(applicationInfo.sourceDir);

String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);

for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
File extractedFile = new File(dexDir, fileName);
if (extractedFile.isFile())
sourcePaths.add(extractedFile.getAbsolutePath());
else
throw new Exception("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}

return sourcePaths;
}

private void scanForModelClasses(File path, String packageName, ClassLoader classLoader) {
if (path.isDirectory()) {
for (File file : path.listFiles()) {
Expand Down Expand Up @@ -206,6 +255,9 @@ else if (ReflectionUtils.isTypeSerializer(discoveredClass)) {
catch (IllegalAccessException e) {
Log.e("IllegalAccessException", e);
}
catch (NoClassDefFoundError e) {
Log.e("Skipping class " + className + " (missing dependency)", e);
}
}
}
}
9 changes: 8 additions & 1 deletion src/com/activeandroid/automigration/TableDifference.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.activeandroid.automigration;

import android.util.Log;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -39,7 +41,12 @@ public TableDifference(TableInfo tableInfo, SQLTableInfo sqlTableInfo) {
if (existingColumnInfo.getType() == sqlColumnInfo.getType()) {
mDifferences.put(sqlColumnInfo, existingColumnInfo);
} else {
throw new IncompatibleColumnTypesException(tableInfo.getTableName(), existingColumnInfo.getName(), existingColumnInfo.getType(), sqlColumnInfo.getType());
// allow column type changes just to let SQLite attempt to cast these values
Log.w(TableDifference.class.getName(), "potentially incompatible column types (table='"
+ tableInfo.getTableName() + "' column='" + existingColumnInfo.getName()
+ "' current type='" + existingColumnInfo.getType() + "' new type='"
+ sqlColumnInfo.getType() + "')");
mDifferences.put(sqlColumnInfo, existingColumnInfo);
}
}
break;
Expand Down
69 changes: 45 additions & 24 deletions src/com/activeandroid/internal/AnnotationProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
Expand Down Expand Up @@ -134,7 +135,8 @@ private void generate(TypeElement tableElement, Set<VariableElement> columns) {

private String getLoadFromCursorCode(Set<VariableElement> columns) {
StringBuilder stringBuilder = new StringBuilder();

stringBuilder.append(" int i = -1; // column index \n");
final String nullCheck = CURSOR + ".isNull(i) ? null : ";
for (VariableElement column : columns) {
Column annotation = column.getAnnotation(Column.class);

Expand All @@ -147,40 +149,56 @@ private String getLoadFromCursorCode(Set<VariableElement> columns) {
boolean notPrimitiveType = typeMirror instanceof DeclaredType;
String type = typeMirror.toString() + ".class";
String getColumnIndex = COLUMNS_ORDERED + ".indexOf(\"" + fieldName + "\")";
String getColumnIndexAssignment = "i = " + getColumnIndex + "; \n";

stringBuilder.append(" " + getColumnIndexAssignment );
if (notPrimitiveType) {
stringBuilder.append(" if (ModelHelper.isSerializable(" + type + ")) {\n");
stringBuilder.append(" " + MODEL + "." + column.getSimpleName() + " = (" + typeMirror.toString() + ") ModelHelper.getSerializable(cursor, " + type + ", " + getColumnIndex + ");\n");
stringBuilder.append(" " + MODEL + "." + column.getSimpleName() + " = (" + typeMirror.toString() + ") ModelHelper.getSerializable(cursor, " + type + ", i);\n");
stringBuilder.append(" } else {\n");
stringBuilder.append(" " + MODEL + "." + column.getSimpleName() + " = ");
} else {
stringBuilder.append(" " + MODEL + "." + column.getSimpleName() + " = ");
}

if (isTypeOf(typeMirror, Integer.class) || isTypeOf(typeMirror, int.class))
stringBuilder.append(CURSOR + ".getInt(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Byte.class) || isTypeOf(typeMirror, byte.class))
stringBuilder.append(CURSOR + ".getInt(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Short.class) || isTypeOf(typeMirror, short.class))
stringBuilder.append(CURSOR + ".getInt(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Long.class) || isTypeOf(typeMirror, long.class))
stringBuilder.append(CURSOR + ".getLong(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Float.class) || isTypeOf(typeMirror, float.class))
stringBuilder.append(CURSOR + ".getFloat(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Double.class) || isTypeOf(typeMirror, double.class))
stringBuilder.append(CURSOR + ".getDouble(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Boolean.class) || isTypeOf(typeMirror, boolean.class))
stringBuilder.append(CURSOR + ".getInt(" + getColumnIndex + ") != 0;\n");
else if (isTypeOf(typeMirror, Character.class) || isTypeOf(typeMirror, char.class))
stringBuilder.append(CURSOR + ".getString(" + getColumnIndex + ");\n");
if (isTypeOf(typeMirror, Integer.class) || isTypeOf(typeMirror, Byte.class) || isTypeOf(typeMirror, Short.class) )
stringBuilder.append(nullCheck).append(CURSOR + ".getInt(i);\n");
else if (isTypeOf(typeMirror, Long.class))
stringBuilder.append(nullCheck).append(CURSOR + ".getLong(i);\n");
else if (isTypeOf(typeMirror, Float.class))
stringBuilder.append(nullCheck).append(CURSOR + ".getFloat(i);\n");
else if (isTypeOf(typeMirror, Double.class))
stringBuilder.append(nullCheck).append(CURSOR + ".getDouble(i);\n");
else if (isTypeOf(typeMirror, int.class))
stringBuilder.append(CURSOR + ".getInt(i);\n");
else if (isTypeOf(typeMirror, byte.class))
stringBuilder.append(CURSOR + ".getInt(i);\n");
else if (isTypeOf(typeMirror, short.class))
stringBuilder.append(CURSOR + ".getInt(i);\n");
else if (isTypeOf(typeMirror, long.class))
stringBuilder.append(CURSOR + ".getLong(i);\n");
else if (isTypeOf(typeMirror, float.class))
stringBuilder.append(CURSOR + ".getFloat(i);\n");
else if (isTypeOf(typeMirror, double.class))
stringBuilder.append(CURSOR + ".getDouble(i);\n");
else if (isTypeOf(typeMirror, Boolean.class))
stringBuilder.append(nullCheck).append(CURSOR + ".getInt(i) != 0;\n");
else if (isTypeOf(typeMirror, boolean.class))
stringBuilder.append(CURSOR + ".getInt(i) != 0;\n");
else if (isTypeOf(typeMirror, char.class))
stringBuilder.append(CURSOR + ".getString(i);\n");
else if (isTypeOf(typeMirror, Character.class))
stringBuilder.append(nullCheck).append(CURSOR + ".getString(i);\n");
else if (isTypeOf(typeMirror, String.class))
stringBuilder.append(CURSOR + ".getString(" + getColumnIndex + ");\n");
else if (isTypeOf(typeMirror, Byte[].class) || isTypeOf(typeMirror, byte[].class))
stringBuilder.append(CURSOR + ".getBlob(" + getColumnIndex + ");\n");
stringBuilder.append(nullCheck).append(CURSOR + ".getString(i);\n");
else if (isTypeOf(typeMirror, byte[].class))
stringBuilder.append(CURSOR + ".getBlob(i);\n");
else if (isTypeOf(typeMirror, Byte[].class))
stringBuilder.append(nullCheck).append(CURSOR + ".getBlob(i);\n");
else if (isTypeOf(typeMirror, Model.class))
stringBuilder.append("(" + typeMirror.toString() + ") ModelHelper.getModel(cursor, " + type + ", " + getColumnIndex + ");\n");
stringBuilder.append("(" + typeMirror.toString() + ") ModelHelper.getModel(cursor, " + type + ", i);\n");
else if (isTypeOf(typeMirror, Enum.class))
stringBuilder.append("(" + typeMirror.toString() + ") ModelHelper.getEnum(cursor, " + type + ", " + getColumnIndex + ");\n");
stringBuilder.append("(" + typeMirror.toString() + ") ModelHelper.getEnum(cursor, " + type + ", i);\n");
else
stringBuilder.append(" null;\n");
if (notPrimitiveType) {
Expand All @@ -205,7 +223,7 @@ private String getFillContentValuesCode(Set<VariableElement> columns) {
boolean notPrimitiveType = typeMirror instanceof DeclaredType;
String type = typeMirror.toString() + ".class";
String getValue = MODEL + "." + column.getSimpleName();

if (notPrimitiveType) {
stringBuilder.append(" if (ModelHelper.isSerializable(" + type + ")) {\n");
stringBuilder.append(" ModelHelper.setSerializable(" + CONTENT_VALUES + ", " + type + ", " + getValue + ", \"" + fieldName + "\");\n");
Expand Down Expand Up @@ -256,6 +274,9 @@ private boolean isTypeOf(TypeMirror typeMirror, Class<?> type) {
if (type.getName().equals(typeMirror.toString()))
return true;

if ((typeMirror.getKind() == TypeKind.ARRAY) && type.isArray())
return typeMirror.toString().equals(type.getComponentType() + "[]");

if (typeMirror instanceof DeclaredType == false)
return false;

Expand Down
16 changes: 14 additions & 2 deletions src/com/activeandroid/query/From.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public final class From implements Sqlable {
private String mOrderBy;
private String mLimit;
private String mOffset;
private boolean useCache = true;

private List<Object> mArguments;

Expand Down Expand Up @@ -295,7 +296,7 @@ public String toCountSql() {

public <T extends Model> List<T> execute() {
if (mQueryBase instanceof Select) {
return SQLiteUtils.rawQuery(mType, toSql(), getArguments());
return SQLiteUtils.rawQuery(mType, toSql(), getArguments(), useCache);

} else {
SQLiteUtils.execSql(toSql(), getArguments());
Expand All @@ -308,7 +309,7 @@ public <T extends Model> List<T> execute() {
public <T extends Model> T executeSingle() {
if (mQueryBase instanceof Select) {
limit(1);
return (T) SQLiteUtils.rawQuerySingle(mType, toSql(), getArguments());
return (T) SQLiteUtils.rawQuerySingle(mType, toSql(), getArguments(), useCache);

} else {
limit(1);
Expand Down Expand Up @@ -343,4 +344,15 @@ public String[] getArguments() {

return args;
}

/**
* Retrieve entities from ActiveAndroid's cache. The cache is enabled by default.
* @param useCache
* @return
*/
public From setUseCache(boolean useCache) {
this.useCache = useCache;
return this;
}

}
Loading