diff --git a/.gitignore b/.gitignore index 20374e4a4..b91594d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ out/ build/ local.properties +/bin/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f1548666d..eea4cd151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,11 @@ Release History --------------- -### V2.1.0 Minor feature and bugfix release (2015-11-12, both core and generator) +### V2.1.0 Minor feature release (2015-09-XX, both core and generator) * Official Robolectric support: workaround for a broken system call in Robolectric triggered by Query.forCurrentThread -* QueryBuilder now allows to create DISTINCT queries to avoid duplicate entities returned * CursorQuery (beta, API might change) -* Deadlock prevention when loading a list of entities while doing concurrent updates * Fixed async queries * Better Android Studio support -* Generator: Possibility to supply custom JavaDoc for entities and their properties +* Added performance tests for: ActiveAndroid, Realm, Parse * Generator: Fixed codeBeforeGetter, added codeBeforeGetterAndSetter ### V2.0.0 Major feature release (2015-07-30, both core and generator) diff --git a/DaoCore/build.gradle b/DaoCore/build.gradle index 4f3e7e2c5..4978cc077 100644 --- a/DaoCore/build.gradle +++ b/DaoCore/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'signing' group = 'de.greenrobot' archivesBaseName = 'greendao' -version = '2.1.0' +version = '2.1.0-SNAPSHOT' sourceCompatibility = 1.6 def isSnapshot = version.endsWith('-SNAPSHOT') @@ -45,7 +45,7 @@ javadoc { failOnError = false classpath += configurations.provided title = " greenDAO ${version} API" - options.bottom = 'Available under the Apache License, Version 2.0 - Copyright © 2011-2015 greenrobot.de. All Rights Reserved.' + options.bottom = 'Available under the Apache License, Version 2.0 - Copyright © 2011-2013 greenrobot.de. All Rights Reserved.' excludes = ['de/greenrobot/dao/internal','de/greenrobot/dao/Internal*'] } diff --git a/DaoCore/src/main/java/de/greenrobot/dao/AbstractDao.java b/DaoCore/src/main/java/de/greenrobot/dao/AbstractDao.java index d39b306ac..51349dfb9 100644 --- a/DaoCore/src/main/java/de/greenrobot/dao/AbstractDao.java +++ b/DaoCore/src/main/java/de/greenrobot/dao/AbstractDao.java @@ -16,19 +16,17 @@ package de.greenrobot.dao; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + import android.database.CrossProcessCursor; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - import de.greenrobot.dao.identityscope.IdentityScope; import de.greenrobot.dao.identityscope.IdentityScopeLong; import de.greenrobot.dao.internal.DaoConfig; @@ -39,12 +37,15 @@ /** * Base class for all DAOs: Implements entity operations like insert, load, delete, and query. - *

+ * * This class is thread-safe. * - * @param Entity type - * @param Primary key (PK) type; use Void if entity does not have exactly one PK * @author Markus + * + * @param + * Entity type + * @param + * Primary key (PK) type; use Void if entity does not have exactly one PK */ /* * When operating on TX, statements, or identity scope the following locking order must be met to avoid deadlocks: @@ -117,7 +118,8 @@ public String[] getNonPkColumns() { /** * Loads and entity for the given PK. * - * @param key a PK value or null + * @param key + * a PK value or null * @return The entity or null, if no entity matched the PK value */ public T load(K key) { @@ -132,13 +134,13 @@ public T load(K key) { } } String sql = statements.getSelectByKey(); - String[] keyArray = new String[]{key.toString()}; + String[] keyArray = new String[] { key.toString() }; Cursor cursor = db.rawQuery(sql, keyArray); return loadUniqueAndCloseCursor(cursor); } public T loadByRowId(long rowId) { - String[] idArray = new String[]{Long.toString(rowId)}; + String[] idArray = new String[] { Long.toString(rowId) }; Cursor cursor = db.rawQuery(statements.getSelectByRowId(), idArray); return loadUniqueAndCloseCursor(cursor); } @@ -188,7 +190,8 @@ protected List loadAllAndCloseCursor(Cursor cursor) { /** * Inserts the given entities in the database using a transaction. * - * @param entities The entities to insert. + * @param entities + * The entities to insert. */ public void insertInTx(Iterable entities) { insertInTx(entities, isEntityUpdateable()); @@ -197,7 +200,8 @@ public void insertInTx(Iterable entities) { /** * Inserts the given entities in the database using a transaction. * - * @param entities The entities to insert. + * @param entities + * The entities to insert. */ public void insertInTx(T... entities) { insertInTx(Arrays.asList(entities), isEntityUpdateable()); @@ -207,8 +211,10 @@ public void insertInTx(T... entities) { * Inserts the given entities in the database using a transaction. The given entities will become tracked if the PK * is set. * - * @param entities The entities to insert. - * @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve performance. + * @param entities + * The entities to insert. + * @param setPrimaryKey + * if true, the PKs of the given will be set after the insert; pass false to improve performance. */ public void insertInTx(Iterable entities, boolean setPrimaryKey) { SQLiteStatement stmt = statements.getInsertStatement(); @@ -219,8 +225,10 @@ public void insertInTx(Iterable entities, boolean setPrimaryKey) { * Inserts or replaces the given entities in the database using a transaction. The given entities will become * tracked if the PK is set. * - * @param entities The entities to insert. - * @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve performance. + * @param entities + * The entities to insert. + * @param setPrimaryKey + * if true, the PKs of the given will be set after the insert; pass false to improve performance. */ public void insertOrReplaceInTx(Iterable entities, boolean setPrimaryKey) { SQLiteStatement stmt = statements.getInsertOrReplaceStatement(); @@ -230,7 +238,8 @@ public void insertOrReplaceInTx(Iterable entities, boolean setPrimaryKey) { /** * Inserts or replaces the given entities in the database using a transaction. * - * @param entities The entities to insert. + * @param entities + * The entities to insert. */ public void insertOrReplaceInTx(Iterable entities) { insertOrReplaceInTx(entities, isEntityUpdateable()); @@ -239,7 +248,8 @@ public void insertOrReplaceInTx(Iterable entities) { /** * Inserts or replaces the given entities in the database using a transaction. * - * @param entities The entities to insert. + * @param entities + * The entities to insert. */ public void insertOrReplaceInTx(T... entities) { insertOrReplaceInTx(Arrays.asList(entities), isEntityUpdateable()); @@ -359,18 +369,12 @@ protected void updateKeyAfterInsertAndAttach(T entity, long rowId, boolean lock) /** Reads all available rows from the given cursor and returns a list of entities. */ protected List loadAllFromCursor(Cursor cursor) { int count = cursor.getCount(); - if (count == 0) { - return Collections.EMPTY_LIST; - } List list = new ArrayList(count); - CursorWindow window = null; - boolean useFastCursor = false; if (cursor instanceof CrossProcessCursor) { - window = ((CrossProcessCursor) cursor).getWindow(); - if (window != null) { // E.g. Robolectric has no Window at this point + CursorWindow window = ((CrossProcessCursor) cursor).getWindow(); + if (window != null) { // E.g. Roboelectric has no Window at this point if (window.getNumRows() == count) { cursor = new FastCursor(window); - useFastCursor = true; } else { DaoLog.d("Window vs. result size: " + window.getNumRows() + "/" + count); } @@ -382,15 +386,10 @@ protected List loadAllFromCursor(Cursor cursor) { identityScope.lock(); identityScope.reserveRoom(count); } - try { - if (!useFastCursor && window != null && identityScope != null) { - loadAllUnlockOnWindowBounds(cursor, window, list); - } else { - do { - list.add(loadCurrent(cursor, 0, false)); - } while (cursor.moveToNext()); - } + do { + list.add(loadCurrent(cursor, 0, false)); + } while (cursor.moveToNext()); } finally { if (identityScope != null) { identityScope.unlock(); @@ -400,42 +399,6 @@ protected List loadAllFromCursor(Cursor cursor) { return list; } - private void loadAllUnlockOnWindowBounds(Cursor cursor, CursorWindow window, List list) { - int windowEnd = window.getStartPosition() + window.getNumRows(); - for (int row = 0; ; row++) { - list.add(loadCurrent(cursor, 0, false)); - row++; - if (row >= windowEnd) { - window = moveToNextUnlocked(cursor); - if(window == null) { - break; - } - windowEnd = window.getStartPosition() + window.getNumRows(); - } else { - if(!cursor.moveToNext()) { - break; - } - } - } - } - - /** - * Unlock identityScope during cursor.moveToNext() when it is about to fill the window (needs a db connection): - * We should not hold the lock while trying to acquire a db connection to avoid deadlocks. - */ - private CursorWindow moveToNextUnlocked(Cursor cursor) { - identityScope.unlock(); - try { - if(cursor.moveToNext()) { - return ((CrossProcessCursor) cursor).getWindow(); - } else { - return null; - } - } finally { - identityScope.lock(); - } - } - /** Internal use only. Considers identity scope. */ final protected T loadCurrent(Cursor cursor, int offset, boolean lock) { if (identityScopeLong != null) { @@ -617,7 +580,8 @@ private void deleteInTxInternal(Iterable entities, Iterable keys) { /** * Deletes the given entities in the database using a transaction. * - * @param entities The entities to delete. + * @param entities + * The entities to delete. */ public void deleteInTx(Iterable entities) { deleteInTxInternal(entities, null); @@ -626,7 +590,8 @@ public void deleteInTx(Iterable entities) { /** * Deletes the given entities in the database using a transaction. * - * @param entities The entities to delete. + * @param entities + * The entities to delete. */ public void deleteInTx(T... entities) { deleteInTxInternal(Arrays.asList(entities), null); @@ -635,7 +600,8 @@ public void deleteInTx(T... entities) { /** * Deletes all entities with the given keys in the database using a transaction. * - * @param keys Keys of the entities to delete. + * @param keys + * Keys of the entities to delete. */ public void deleteByKeyInTx(Iterable keys) { deleteInTxInternal(null, keys); @@ -644,7 +610,8 @@ public void deleteByKeyInTx(Iterable keys) { /** * Deletes all entities with the given keys in the database using a transaction. * - * @param keys Keys of the entities to delete. + * @param keys + * Keys of the entities to delete. */ public void deleteByKeyInTx(K... keys) { deleteInTxInternal(null, Arrays.asList(keys)); @@ -655,7 +622,7 @@ public void refresh(T entity) { assertSinglePk(); K key = getKeyVerified(entity); String sql = statements.getSelectByKey(); - String[] keyArray = new String[]{key.toString()}; + String[] keyArray = new String[] { key.toString() }; Cursor cursor = db.rawQuery(sql, keyArray); try { boolean available = cursor.moveToFirst(); @@ -716,9 +683,11 @@ protected void updateInsideSynchronized(T entity, SQLiteStatement stmt, boolean /** * Attaches the entity to the identity scope. Calls attachEntity(T entity). * - * @param key Needed only for identity scope, pass null if there's none. - * @param entity The entitiy to attach - */ + * @param key + * Needed only for identity scope, pass null if there's none. + * @param entity + * The entitiy to attach + * */ protected final void attachEntity(K key, T entity, boolean lock) { attachEntity(entity); if (identityScope != null && key != null) { @@ -734,15 +703,17 @@ protected final void attachEntity(K key, T entity, boolean lock) { * Sub classes with relations additionally set the DaoMaster here. Must be called before the entity is attached to * the identity scope. * - * @param entity The entitiy to attach - */ + * @param entity + * The entitiy to attach + * */ protected void attachEntity(T entity) { } /** * Updates the given entities in the database using a transaction. * - * @param entities The entities to insert. + * @param entities + * The entities to insert. */ public void updateInTx(Iterable entities) { SQLiteStatement stmt = statements.getUpdateStatement(); @@ -783,7 +754,8 @@ public void updateInTx(Iterable entities) { /** * Updates the given entities in the database using a transaction. * - * @param entities The entities to update. + * @param entities + * The entities to update. */ public void updateInTx(T... entities) { updateInTx(Arrays.asList(entities)); diff --git a/DaoCore/src/main/java/de/greenrobot/dao/Property.java b/DaoCore/src/main/java/de/greenrobot/dao/Property.java index 77923eb71..eddd6e405 100644 --- a/DaoCore/src/main/java/de/greenrobot/dao/Property.java +++ b/DaoCore/src/main/java/de/greenrobot/dao/Property.java @@ -33,13 +33,17 @@ public class Property { public final String name; public final boolean primaryKey; public final String columnName; + public final String sqlType; + public final int version; - public Property(int ordinal, Class type, String name, boolean primaryKey, String columnName) { + public Property(int ordinal, Class type, String name, boolean primaryKey, String columnName, String sqlType, int version) { this.ordinal = ordinal; this.type = type; this.name = name; this.primaryKey = primaryKey; this.columnName = columnName; + this.sqlType = sqlType; + this.version = version; } /** Creates an "equal ('=')" condition for this property. */ diff --git a/DaoCore/src/main/java/de/greenrobot/dao/internal/SqlUtils.java b/DaoCore/src/main/java/de/greenrobot/dao/internal/SqlUtils.java index 3e328bc95..b9cdf407e 100644 --- a/DaoCore/src/main/java/de/greenrobot/dao/internal/SqlUtils.java +++ b/DaoCore/src/main/java/de/greenrobot/dao/internal/SqlUtils.java @@ -104,12 +104,12 @@ public static String createSqlInsert(String insertInto, String tablename, String } /** Creates an select for given columns with a trailing space */ - public static String createSqlSelect(String tablename, String tableAlias, String[] columns, boolean distinct) { + public static String createSqlSelect(String tablename, String tableAlias, String[] columns) { if (tableAlias == null || tableAlias.length() < 0) { throw new DaoException("Table alias required"); } - StringBuilder builder = new StringBuilder(distinct ? "SELECT DISTINCT " : "SELECT "); + StringBuilder builder = new StringBuilder("SELECT "); SqlUtils.appendColumns(builder, tableAlias, columns).append(" FROM "); builder.append('"').append(tablename).append('"').append(' ').append(tableAlias).append(' '); return builder.toString(); diff --git a/DaoCore/src/main/java/de/greenrobot/dao/internal/TableStatements.java b/DaoCore/src/main/java/de/greenrobot/dao/internal/TableStatements.java index 864b6e04e..9414f1f71 100644 --- a/DaoCore/src/main/java/de/greenrobot/dao/internal/TableStatements.java +++ b/DaoCore/src/main/java/de/greenrobot/dao/internal/TableStatements.java @@ -77,7 +77,7 @@ public SQLiteStatement getUpdateStatement() { /** ends with an space to simplify appending to this string. */ public String getSelectAll() { if (selectAll == null) { - selectAll = SqlUtils.createSqlSelect(tablename, "T", allColumns, false); + selectAll = SqlUtils.createSqlSelect(tablename, "T", allColumns); } return selectAll; } @@ -85,7 +85,7 @@ public String getSelectAll() { /** ends with an space to simplify appending to this string. */ public String getSelectKeys() { if (selectKeys == null) { - selectKeys = SqlUtils.createSqlSelect(tablename, "T", pkColumns, false); + selectKeys = SqlUtils.createSqlSelect(tablename, "T", pkColumns); } return selectKeys; } diff --git a/DaoCore/src/main/java/de/greenrobot/dao/query/Join.java b/DaoCore/src/main/java/de/greenrobot/dao/query/Join.java index 04dba3fe2..ab142a068 100644 --- a/DaoCore/src/main/java/de/greenrobot/dao/query/Join.java +++ b/DaoCore/src/main/java/de/greenrobot/dao/query/Join.java @@ -80,12 +80,4 @@ public WhereCondition and(WhereCondition cond1, WhereCondition cond2, WhereCondi return whereCollector.combineWhereConditions(" AND ", cond1, cond2, condMore); } - /** - * Usually you don't need this value; just in case you are mixing custom - * {@link de.greenrobot.dao.query.WhereCondition.StringCondition} into the query, this value allows to reference - * the joined (target) table. - */ - public String getTablePrefix() { - return tablePrefix; - } } diff --git a/DaoCore/src/main/java/de/greenrobot/dao/query/QueryBuilder.java b/DaoCore/src/main/java/de/greenrobot/dao/query/QueryBuilder.java index 2e6218381..e6c15ec67 100644 --- a/DaoCore/src/main/java/de/greenrobot/dao/query/QueryBuilder.java +++ b/DaoCore/src/main/java/de/greenrobot/dao/query/QueryBuilder.java @@ -58,7 +58,6 @@ public class QueryBuilder { private Integer limit; private Integer offset; - private boolean distinct; /** For internal use by greenDAO only. */ public static QueryBuilder internalCreate(AbstractDao dao) { @@ -85,12 +84,6 @@ private void checkOrderBuilder() { } } - /** Use a SELECT DISTINCT to avoid duplicate entities returned, e.g. when doing joins. */ - public QueryBuilder distinct() { - distinct = true; - return this; - } - /** * Adds the given conditions to the where clause using an logical AND. To create new conditions, use the properties * given in the generated dao classes. @@ -269,7 +262,7 @@ public CursorQuery buildCursor() { } private StringBuilder createSelectBuilder() { - String select = SqlUtils.createSqlSelect(dao.getTablename(), tablePrefix, dao.getAllColumns(), distinct); + String select = SqlUtils.createSqlSelect(dao.getTablename(), tablePrefix, dao.getAllColumns()); StringBuilder builder = new StringBuilder(select); appendJoinsAndWheres(builder, tablePrefix); @@ -280,6 +273,7 @@ private StringBuilder createSelectBuilder() { return builder; } + private int checkAddLimit(StringBuilder builder) { int limitPosition = -1; if (limit != null) { diff --git a/DaoGenerator/build.gradle b/DaoGenerator/build.gradle index 843f81412..ccdbde8b1 100644 --- a/DaoGenerator/build.gradle +++ b/DaoGenerator/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'signing' group = 'de.greenrobot' archivesBaseName = 'greendao-generator' -version = '2.1.1-SNAPSHOT' -sourceCompatibility = 1.7 +version = '2.0.0' +sourceCompatibility = 1.6 def isSnapshot = version.endsWith('-SNAPSHOT') def sonatypeRepositoryUrl @@ -27,7 +27,7 @@ configurations { } dependencies { - compile 'org.freemarker:freemarker:2.3.23' + compile 'org.freemarker:freemarker:2.3.22' testCompile 'junit:junit:4.12' // deployerJars 'org.apache.maven.wagon:wagon-webdav-jackrabbit:2.4' deployerJars 'org.apache.maven.wagon:wagon-webdav:1.0-beta-2' diff --git a/DaoGenerator/src-template/dao-master.ftl b/DaoGenerator/src-template/dao-master.ftl index adaffc487..6f6bd55e3 100644 --- a/DaoGenerator/src-template/dao-master.ftl +++ b/DaoGenerator/src-template/dao-master.ftl @@ -81,6 +81,56 @@ public class DaoMaster extends AbstractDaoMaster { dropAllTables(db, true); onCreate(db); } + + } + + /** WARNING: Drops all table on Upgrade! Use only during development. */ + public static class AutoUpdateOpenHelper extends OpenHelper { + public AutoUpdateOpenHelper(Context context, String name, CursorFactory factory) { + super(context, name, factory); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.beginTransaction(); + try { +<#list schema.entities as entity> + ${entity.classNameDao}.updateTable(db, oldVersion, newVersion); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + } + + } + + /** WARNING: Drops all table on Upgrade! Use only during development. */ + public static class DevAutoUpdateOpenHelper extends OpenHelper { + public DevAutoUpdateOpenHelper(Context context, String name, CursorFactory factory) { + super(context, name, factory); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.beginTransaction(); + try { +<#list schema.entities as entity> + ${entity.classNameDao}.updateTable(db, oldVersion, newVersion); + + + } catch (Exception e) { + e.printStackTrace(); + Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); + dropAllTables(db, true); + onCreate(db); + } finally { + db.endTransaction(); + } + + } + } public DaoMaster(SQLiteDatabase db) { diff --git a/DaoGenerator/src-template/dao.ftl b/DaoGenerator/src-template/dao.ftl index 60f7045b1..bdf9a272a 100644 --- a/DaoGenerator/src-template/dao.ftl +++ b/DaoGenerator/src-template/dao.ftl @@ -30,6 +30,7 @@ import java.util.ArrayList; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; +import android.util.Log; import de.greenrobot.dao.AbstractDao; import de.greenrobot.dao.Property; @@ -64,6 +65,8 @@ import ${entity.javaPackage}.${entity.className}.Builder; public class ${entity.classNameDao} extends AbstractDao<${entity.className}, ${entity.pkType}> { public static final String TABLENAME = "${entity.tableName}"; + + public static final String TAG = "${entity.classNameDao}"; /** * Properties of entity ${entity.className}.
@@ -71,8 +74,15 @@ public class ${entity.classNameDao} extends AbstractDao<${entity.className}, ${e */ public static class Properties { <#list entity.propertiesColumns as property> - public final static Property ${property.propertyName?cap_first} = new Property(${property_index}, ${property.javaType}.class, "${property.propertyName}", ${property.primaryKey?string}, "${property.columnName}"); + public final static Property ${property.propertyName?cap_first} = new Property(${property_index}, ${property.javaType}.class, "${property.propertyName}", ${property.primaryKey?string}, "${property.columnName}", "${property.columnType}", ${property.version}); + + public final static Property [] all = { +<#list entity.propertiesColumns as property> + ${property.propertyName?cap_first}, + + }; + }; <#if entity.active> @@ -114,6 +124,31 @@ as property>\"${property.columnName}\"<#if property_has_next>,);") } + + public static void updateTable(SQLiteDatabase db, int oldVer, int newVer) { + for (Property p : Properties.all) { + if (p.version > oldVer) { + Cursor cursor = db.rawQuery("PRAGMA table_info("+ TABLENAME +")", null); + if (cursor != null) { + boolean skip = false; + while (cursor.moveToNext()) { + String name = cursor.getString(cursor.getColumnIndex("name")); + if (p.columnName.equalsIgnoreCase(name)) { + skip = true; + break; + } + } + if (skip) { + Log.w(TAG, "Skipping add column '" + p.columnName + "' because already exists"); + continue; + } + cursor.close(); + } + Log.i(TAG, "Alter table " + TABLENAME + " add column '" + p.columnName + "' " + p.sqlType); + db.execSQL("ALTER TABLE \"" + TABLENAME + "\" ADD \"" + p.columnName + "\" " + p.sqlType); + } + } + } /** Drops the underlying database table. */ public static void dropTable(SQLiteDatabase db, boolean ifExists) { diff --git a/DaoGenerator/src-template/entity.ftl b/DaoGenerator/src-template/entity.ftl index 0e383d847..5fd44b8b0 100644 --- a/DaoGenerator/src-template/entity.ftl +++ b/DaoGenerator/src-template/entity.ftl @@ -44,17 +44,9 @@ import ${additionalImport}; <#else> // THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. Enable "keep" sections if you want to edit. -<#if entity.javaDoc ??> - -${entity.javaDoc} -<#else> /** * Entity mapped to table "${entity.tableName}". */ - -<#if entity.codeBeforeClass ??> -${entity.codeBeforeClass} - public class ${entity.className}<#if entity.superclass?has_content> extends ${entity.superclass} <#if entity.interfacesToImplement?has_content> implements <#list entity.interfacesToImplement @@ -64,9 +56,6 @@ as ifc>${ifc}<#if ifc_has_next>, { <#if property.notNull && complexTypes?seq_contains(property.propertyType)> /** Not-null value. */ -<#if property.javaDocField ??> -${property.javaDocField} - <#if property.codeBeforeField ??> ${property.codeBeforeField} @@ -112,6 +101,36 @@ property>${property.javaType} ${property.propertyName}<#if property_has_next>, < } +<#if entity.parcelable> + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { +<#list entity.properties as property> + ${property.parcelableWriteStatement} + + } + + private ${entity.className}(Parcel in) { +<#list entity.properties as property> + ${property.parcelableReadStatement} + + } + + public static final Parcelable.Creator<${entity.className}> CREATOR = new Parcelable.Creator<${entity.className}>() { + public ${entity.className} createFromParcel(Parcel in) { + return new ${entity.className}(in); + } + + public ${entity.className} [] newArray(int size) { + return new ${entity.className}[size]; + } + }; + + public ${entity.className}(<#list entity.properties as property>${property.javaTypeInEntity} ${property.propertyName}<#if property_has_next>, ) { <#list entity.properties as property> @@ -132,9 +151,6 @@ property>${property.javaTypeInEntity} ${property.propertyName}<#if property_has_ <#if property.notNull && complexTypes?seq_contains(property.propertyType)> /** Not-null value. */ -<#if property.javaDocGetter ??> -${property.javaDocGetter} - <#if property.codeBeforeGetter ??> ${property.codeBeforeGetter} @@ -145,9 +161,6 @@ ${property.javaDocGetter} <#if property.notNull && complexTypes?seq_contains(property.propertyType)> /** Not-null value; ensure this value is available before it is saved to the database. */ -<#if property.javaDocSetter ??> -${property.javaDocSetter} - <#if property.codeBeforeSetter ??> ${property.codeBeforeSetter} diff --git a/DaoGenerator/src/de/greenrobot/daogenerator/DaoGenerator.java b/DaoGenerator/src/de/greenrobot/daogenerator/DaoGenerator.java index bb25fe9a1..0828ca40c 100644 --- a/DaoGenerator/src/de/greenrobot/daogenerator/DaoGenerator.java +++ b/DaoGenerator/src/de/greenrobot/daogenerator/DaoGenerator.java @@ -28,6 +28,7 @@ import java.util.regex.Pattern; import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; /** @@ -57,8 +58,9 @@ public DaoGenerator() throws IOException { patternKeepFields = compilePattern("FIELDS"); patternKeepMethods = compilePattern("METHODS"); - Configuration config = new Configuration(Configuration.VERSION_2_3_23); + Configuration config = new Configuration(); config.setClassForTemplateLoading(this.getClass(), "/"); + config.setObjectWrapper(new DefaultObjectWrapper()); templateDao = config.getTemplate("dao.ftl"); templateDaoMaster = config.getTemplate("dao-master.ftl"); diff --git a/DaoGenerator/src/de/greenrobot/daogenerator/DaoUtil.java b/DaoGenerator/src/de/greenrobot/daogenerator/DaoUtil.java index a8a2e9658..feb76469c 100644 --- a/DaoGenerator/src/de/greenrobot/daogenerator/DaoUtil.java +++ b/DaoGenerator/src/de/greenrobot/daogenerator/DaoUtil.java @@ -104,11 +104,5 @@ public static int copyAllBytes(InputStream in, OutputStream out) throws IOExcept return byteCount; } - public static String checkConvertToJavaDoc(String javaDoc, String indent) { - if (javaDoc != null && !javaDoc.trim().startsWith("/**")) { - javaDoc = javaDoc.replace("\n", "\n" + indent + " * "); - javaDoc = indent + "/**\n" + indent + " * " + javaDoc + "\n" + indent + " */"; - } - return javaDoc; - } + } diff --git a/DaoGenerator/src/de/greenrobot/daogenerator/Entity.java b/DaoGenerator/src/de/greenrobot/daogenerator/Entity.java index da865a423..2d46158e3 100644 --- a/DaoGenerator/src/de/greenrobot/daogenerator/Entity.java +++ b/DaoGenerator/src/de/greenrobot/daogenerator/Entity.java @@ -17,6 +17,8 @@ */ package de.greenrobot.daogenerator; +import de.greenrobot.daogenerator.Property.PropertyBuilder; + import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -24,8 +26,6 @@ import java.util.Set; import java.util.TreeSet; -import de.greenrobot.daogenerator.Property.PropertyBuilder; - /** * Model class for an entity: a Java data object mapped to a data base table. A new entity is added to a {@link Schema} * by the method {@link Schema#addEntity(String)} (there is no public constructor for {@link Entity} itself).
@@ -64,8 +64,6 @@ public class Entity { private Property pkProperty; private String pkType; private String superclass; - private String javaDoc; - private String codeBeforeClass; private boolean protobuf; private boolean constructors; @@ -74,6 +72,7 @@ public class Entity { private boolean skipTableCreation; private Boolean active; private Boolean hasKeepSections; + private boolean parcelable = false; Entity(Schema schema, String className) { this.schema = schema; @@ -444,41 +443,41 @@ public List getContentProviders() { return contentProviders; } - public void implementsInterface(String... interfaces) { + public Entity implementsInterface(String... interfaces) { for (String interfaceToImplement : interfaces) { if (interfacesToImplement.contains(interfaceToImplement)) { throw new RuntimeException("Interface defined more than once: " + interfaceToImplement); } interfacesToImplement.add(interfaceToImplement); } + return this; } public void implementsSerializable() { interfacesToImplement.add("java.io.Serializable"); } + + /** + * Generates all the Android Parcelable methods. Only Fields marked as notNull() will be parceled. + * @return + */ + public Entity implementsParcelable() { + interfacesToImplement.add("Parcelable"); + parcelable = true; + return addImport("android.os.*"); + } + + public boolean isParcelable() { + return parcelable; + } public String getSuperclass() { return superclass; } - public void setSuperclass(String classToExtend) { + public Entity setSuperclass(String classToExtend) { this.superclass = classToExtend; - } - - public String getJavaDoc() { - return javaDoc; - } - - public void setJavaDoc(String javaDoc) { - this.javaDoc = DaoUtil.checkConvertToJavaDoc(javaDoc, ""); - } - - public String getCodeBeforeClass() { - return codeBeforeClass; - } - - public void setCodeBeforeClass(String codeBeforeClass) { - this.codeBeforeClass = codeBeforeClass; + return this; } void init2ndPass() { @@ -596,6 +595,12 @@ void init3rdPass() { init3rdPassRelations(); init3rdPassAdditionalImports(); } + + void initParcelablePass() { + for (Property property: properties) { + property.initParcelableMethods(); + } + } private void init3rdPassRelations() { Set toOneNames = new HashSet(); diff --git a/DaoGenerator/src/de/greenrobot/daogenerator/Property.java b/DaoGenerator/src/de/greenrobot/daogenerator/Property.java index 14534df55..a7bdc48a8 100644 --- a/DaoGenerator/src/de/greenrobot/daogenerator/Property.java +++ b/DaoGenerator/src/de/greenrobot/daogenerator/Property.java @@ -131,35 +131,13 @@ public PropertyBuilder codeBeforeGetterAndSetter(String code) { return this; } - public PropertyBuilder javaDocField(String javaDoc) { - property.javaDocField = checkConvertToJavaDoc(javaDoc); - return this; - } - - private String checkConvertToJavaDoc(String javaDoc) { - return DaoUtil.checkConvertToJavaDoc(javaDoc, " "); - } - - public PropertyBuilder javaDocGetter(String javaDoc) { - property.javaDocGetter = checkConvertToJavaDoc(javaDoc); - return this; - } - - public PropertyBuilder javaDocSetter(String javaDoc) { - property.javaDocSetter = checkConvertToJavaDoc(javaDoc); - return this; - } - - public PropertyBuilder javaDocGetterAndSetter(String javaDoc) { - javaDoc = checkConvertToJavaDoc(javaDoc); - property.javaDocGetter = javaDoc; - property.javaDocSetter = javaDoc; - return this; - } - public Property getProperty() { return property; } + + public void setVersion(int rawVersionDontForgetToBump) { + property.version = rawVersionDontForgetToBump; + } } private final Schema schema; @@ -179,10 +157,6 @@ public Property getProperty() { private String codeBeforeGetter; private String codeBeforeSetter; - private String javaDocField; - private String javaDocGetter; - private String javaDocSetter; - private boolean primaryKey; private boolean pkAsc; private boolean pkDesc; @@ -197,6 +171,10 @@ public Property getProperty() { private int ordinal; private String javaType; + private String parcelableWriteStatement = ""; + private String parcelableReadStatement = ""; + + private int version = -1; public Property(Schema schema, Entity entity, PropertyType propertyType, String propertyName) { this.schema = schema; @@ -249,7 +227,15 @@ public String getJavaType() { return javaType; } - public String getJavaTypeInEntity() { + public final String getParcelableWriteStatement() { + return parcelableWriteStatement; + } + + public final String getParcelableReadStatement() { + return parcelableReadStatement; + } + + public String getJavaTypeInEntity() { if (customTypeClassName != null) { return customTypeClassName; } else { @@ -293,18 +279,6 @@ public String getCodeBeforeSetter() { return codeBeforeSetter; } - public String getJavaDocField() { - return javaDocField; - } - - public String getJavaDocGetter() { - return javaDocGetter; - } - - public String getJavaDocSetter() { - return javaDocSetter; - } - public String getDatabaseValueExpression() { return getDatabaseValueExpression(propertyName); } @@ -312,6 +286,14 @@ public String getDatabaseValueExpression() { public String getDatabaseValueExpressionNotNull() { return getDatabaseValueExpression("entity.get" + DaoUtil.capFirst(propertyName) + "()"); } + + /** + * version this field was added. can be used in the onUpgrade callback to auto update. + * @return + */ + public String getVersion() { + return String.valueOf(version); + } // Got too messy in template: // <#if property.customType?has_content>${property.propertyName}Converter.convertToDatabaseValue(<#-- @@ -326,9 +308,9 @@ public String getDatabaseValueExpression(String entityValue) { if (customType != null) { builder.append(')'); } - if (propertyType == PropertyType.Boolean) { + if(propertyType == PropertyType.Boolean) { builder.append(" ? 1L: 0L"); - } else if (propertyType == PropertyType.Date) { + } else if(propertyType == PropertyType.Date) { builder.append(".getTime()"); } return builder.toString(); @@ -345,15 +327,16 @@ public String getEntityValueExpression(String databaseValue) { if (customType != null) { builder.append(propertyName).append("Converter.convertToEntityProperty("); } - if (propertyType == PropertyType.Byte) { + if(propertyType == PropertyType.Byte) { builder.append("(byte) "); - } else if (propertyType == PropertyType.Date) { + }else + if(propertyType == PropertyType.Date) { builder.append("new java.util.Date("); } builder.append(databaseValue); - if (propertyType == PropertyType.Boolean) { + if(propertyType == PropertyType.Boolean) { builder.append(" != 0"); - } else if (propertyType == PropertyType.Date) { + } else if(propertyType == PropertyType.Date) { builder.append(")"); } if (customType != null) { @@ -412,6 +395,81 @@ private void initConstraint() { void init3ndPass() { // Nothing to do so far } + + void initParcelableMethods() { + switch (propertyType) { + case Boolean: + parcelableReadStatement = propertyName + " = in.readByte() != 0;"; + if (notNull) { + parcelableWriteStatement = "out.writeByte((byte)(" + propertyName + " ? 1 : 0));"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeByte((byte)0); else out.writeByte((byte)(" + propertyName + " ? 1 : 0));"; + } + break; + case Byte: + parcelableReadStatement = propertyName + " = in.readByte();"; + if (notNull) { + parcelableWriteStatement = "out.writeByte(" + propertyName + ");"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeByte((byte)0); else out.writeByte(" + propertyName + ");"; + } + break; + case ByteArray: + if (notNull) { + parcelableReadStatement = propertyName + "in.readByteArray(" + propertyName + ");"; + parcelableWriteStatement = "out.writeByteArray(" + propertyName + ");"; + } + break; + case Date: + parcelableReadStatement = propertyName + " = new java.util.Date(in.readLong());"; + parcelableWriteStatement = "out.writeLong(" + propertyName + " == null ? 0 : " + propertyName + ".getTime());"; + break; + case Double: + parcelableReadStatement = propertyName + " = in.readDouble();"; + if (notNull) { + parcelableWriteStatement = "out.writeDouble(" + propertyName + ");"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeDouble(0); else out.writeDouble(" + propertyName + ");"; + } + break; + case Float: + parcelableReadStatement = propertyName + " = in.readFloat();"; + if (notNull) { + parcelableWriteStatement = "out.writeFloat(" + propertyName + ");"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeFloat(0); else out.writeFloat(" + propertyName + ");"; + } + break; + case Int: + parcelableReadStatement = propertyName + " = in.readInt();"; + if (notNull) { + parcelableWriteStatement = "out.writeInt(" + propertyName + ");"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeInt(0); else out.writeInt(" + propertyName + ");"; + } + break; + case Long: + parcelableReadStatement = propertyName + " = in.readLong();"; + if (notNull) { + parcelableWriteStatement = "out.writeLong(" + propertyName + ");"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeLong(0); else out.writeLong(" + propertyName + ");"; + } + break; + case Short: + parcelableReadStatement = propertyName + " = in.readInt();"; + if (notNull) { + parcelableWriteStatement = "out.writeInt(" + propertyName + ");"; + } else { + parcelableWriteStatement = "if (" + propertyName + " == null) out.writeInt(0); else out.writeInt(" + propertyName + ");"; + } + break; + case String: + parcelableReadStatement = propertyName + " = in.readString();"; + parcelableWriteStatement = "out.writeString(" + propertyName + ");"; + break; + } + } @Override public String toString() { diff --git a/DaoGenerator/src/de/greenrobot/daogenerator/Schema.java b/DaoGenerator/src/de/greenrobot/daogenerator/Schema.java index 8351ecb55..236a17687 100644 --- a/DaoGenerator/src/de/greenrobot/daogenerator/Schema.java +++ b/DaoGenerator/src/de/greenrobot/daogenerator/Schema.java @@ -183,6 +183,9 @@ void init2ndPass() { void init3rdPass() { for (Entity entity : entities) { entity.init3rdPass(); + if (entity.isParcelable()) { + entity.initParcelablePass(); + } } } diff --git a/DaoGenerator/src/de/greenrobot/daogenerator/ToManyWithJoinEntity.java b/DaoGenerator/src/de/greenrobot/daogenerator/ToManyWithJoinEntity.java index 0f897f054..752215669 100644 --- a/DaoGenerator/src/de/greenrobot/daogenerator/ToManyWithJoinEntity.java +++ b/DaoGenerator/src/de/greenrobot/daogenerator/ToManyWithJoinEntity.java @@ -45,8 +45,8 @@ public Property getTargetProperty() { return targetProperty; } - void init3rdPass() { - super.init3rdPass(); + void init2ndPass() { + super.init2ndPass(); List pks = sourceEntity.getPropertiesPk(); if (pks.isEmpty()) { throw new RuntimeException("Source entity has no primary key, but we need it for " + this); @@ -57,4 +57,8 @@ void init3rdPass() { } } + void init3rdPass() { + super.init3rdPass(); + } + } diff --git a/DaoTest/build.gradle b/DaoTest/build.gradle index 0947debdd..74707af29 100644 --- a/DaoTest/build.gradle +++ b/DaoTest/build.gradle @@ -12,6 +12,7 @@ apply plugin: 'com.android.application' dependencies { androidTestCompile project(':DaoCore') + androidTestCompile project(':PerformanceTests:Common') testCompile project(':DaoCore') testCompile 'org.robolectric:robolectric:3.0' diff --git a/DaoTest/src-gen/de/greenrobot/daotest/TestEntity.java b/DaoTest/src-gen/de/greenrobot/daotest/TestEntity.java index 15863049b..748cee08a 100644 --- a/DaoTest/src-gen/de/greenrobot/daotest/TestEntity.java +++ b/DaoTest/src-gen/de/greenrobot/daotest/TestEntity.java @@ -1,17 +1,11 @@ package de.greenrobot.daotest; // THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. Enable "keep" sections if you want to edit. - /** - * This entity is used by internal tests of greenDAO. - * (This JavaDoc is defined in the generator project.) + * Entity mapped to table "TEST_ENTITY". */ -// This is another test comment, you could also apply annotations like this public class TestEntity { - /** - * JavaDoc test field - */ private Long id; private int simpleInt; private Integer simpleInteger; @@ -52,9 +46,6 @@ public void setId(Long id) { this.id = id; } - /** - * JavaDoc test getter - */ public int getSimpleInt() { return simpleInt; } @@ -67,25 +58,16 @@ public Integer getSimpleInteger() { return simpleInteger; } - /** - * JavaDoc test setter - */ public void setSimpleInteger(Integer simpleInteger) { this.simpleInteger = simpleInteger; } /** Not-null value. */ - /** - * JavaDoc test getter and setter - */ public String getSimpleStringNotNull() { return simpleStringNotNull; } /** Not-null value; ensure this value is available before it is saved to the database. */ - /** - * JavaDoc test getter and setter - */ public void setSimpleStringNotNull(String simpleStringNotNull) { this.simpleStringNotNull = simpleStringNotNull; } diff --git a/DaoTest/src/de/greenrobot/daotest/performance/IndexedStringPerformanceTest.java b/DaoTest/src/de/greenrobot/daotest/performance/IndexedStringPerformanceTest.java index dfb30b633..1a0822ec3 100644 --- a/DaoTest/src/de/greenrobot/daotest/performance/IndexedStringPerformanceTest.java +++ b/DaoTest/src/de/greenrobot/daotest/performance/IndexedStringPerformanceTest.java @@ -4,7 +4,7 @@ import de.greenrobot.dao.test.AbstractDaoTest; import de.greenrobot.daotest.IndexedStringEntity; import de.greenrobot.daotest.IndexedStringEntityDao; - +import de.greenrobot.performance.StringGenerator; import java.util.ArrayList; import java.util.List; diff --git a/DaoTest/src/de/greenrobot/daotest/query/QueryBuilderSimpleTest.java b/DaoTest/src/de/greenrobot/daotest/query/QueryBuilderSimpleTest.java index 221a80bde..a8dbcc4bc 100644 --- a/DaoTest/src/de/greenrobot/daotest/query/QueryBuilderSimpleTest.java +++ b/DaoTest/src/de/greenrobot/daotest/query/QueryBuilderSimpleTest.java @@ -250,14 +250,4 @@ public void testLike() { assertNull(entity2); } - public void testDistinct() { - TestEntity entity = insert(3).get(1); - - Query query = dao.queryBuilder().distinct() - .where(Properties.SimpleString.eq(entity.getSimpleString())).build(); - TestEntity entity2 = query.uniqueOrThrow(); - assertEquals(entity.getId(), entity2.getId()); - // TODO improve test to check functionality - } - } diff --git a/DaoTestGenerator/src/de/greenrobot/daogenerator/gentest/TestDaoGenerator.java b/DaoTestGenerator/src/de/greenrobot/daogenerator/gentest/TestDaoGenerator.java index f42d1d36e..88ba60800 100644 --- a/DaoTestGenerator/src/de/greenrobot/daogenerator/gentest/TestDaoGenerator.java +++ b/DaoTestGenerator/src/de/greenrobot/daogenerator/gentest/TestDaoGenerator.java @@ -105,13 +105,10 @@ protected void createSimpleNotNull() { protected Entity createTest() { Entity testEntity = schema.addEntity("TestEntity"); - testEntity.setJavaDoc("This entity is used by internal tests of greenDAO.\n" + - "(This JavaDoc is defined in the generator project.)"); - testEntity.setCodeBeforeClass("// This is another test comment, you could also apply annotations like this"); - testEntity.addIdProperty().javaDocField("JavaDoc test field"); - testEntity.addIntProperty("simpleInt").notNull().javaDocGetter("JavaDoc test getter"); - testEntity.addIntProperty("simpleInteger").javaDocSetter("JavaDoc test setter"); - testEntity.addStringProperty("simpleStringNotNull").notNull().javaDocGetterAndSetter("JavaDoc test getter and setter"); + testEntity.addIdProperty(); + testEntity.addIntProperty("simpleInt").notNull(); + testEntity.addIntProperty("simpleInteger"); + testEntity.addStringProperty("simpleStringNotNull").notNull(); testEntity.addStringProperty("simpleString"); testEntity.addStringProperty("indexedString").index(); testEntity.addStringProperty("indexedStringAscUnique").indexAsc(null, true); diff --git a/README.md b/README.md index 5df7e6941..2560bd888 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -**Please help us with this short survey: http://bit.ly/greendao-survey Thanks for your support!** - greenDAO ======== greenDAO is a light & fast ORM solution for Android that maps objects to SQLite databases. Being highly optimized for Android, greenDAO offers great performance and consumes minimal memory. -**Home page, documentation, and support links: http://greenrobot.org/greendao/** +**Home page, documentation, and support links: http://greendao-orm.com/** [![Build Status](https://travis-ci.org/greenrobot/greenDAO.svg?branch=master)](https://travis-ci.org/greenrobot/greenDAO) diff --git a/settings.gradle b/settings.gradle index ef5f41d27..660b6cd5b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,12 @@ include 'DaoCore', 'DaoGenerator', 'DaoExampleGenerator' include 'DaoTest' // Travis needs some Android setup for this include 'DaoTestGenerator' include ':DaoExample' - +include ':PerformanceTests:Common' +include ':PerformanceTests:ActiveAndroid' +include ':PerformanceTests:Couchbase' +include ':PerformanceTests:Cupboard' +include ':PerformanceTests:Firebase' +include ':PerformanceTests:OrmLite' +include ':PerformanceTests:Parse' +include ':PerformanceTests:Realm' +include ':PerformanceTests:Sqlite'