OSDN Git Service

android-2.1_r1 snapshot
[android-x86/packages-apps-Gallery2.git] / src / com / cooliris / picasa / EntrySchema.java
1 package com.cooliris.picasa;
2
3 import java.lang.reflect.AnnotatedElement;
4 import java.lang.reflect.Field;
5 import java.util.ArrayList;
6
7 import android.content.ContentValues;
8 import android.database.Cursor;
9 import android.database.sqlite.SQLiteDatabase;
10 import android.util.Log;
11
12 public final class EntrySchema {
13     public static final int TYPE_STRING = 0;
14     public static final int TYPE_BOOLEAN = 1;
15     public static final int TYPE_SHORT = 2;
16     public static final int TYPE_INT = 3;
17     public static final int TYPE_LONG = 4;
18     public static final int TYPE_FLOAT = 5;
19     public static final int TYPE_DOUBLE = 6;
20     public static final int TYPE_BLOB = 7;
21     public static final String SQLITE_TYPES[] = { "TEXT", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "REAL", "REAL", "NONE" };
22
23     private static final String TAG = "SchemaInfo";
24     private static final String FULL_TEXT_INDEX_SUFFIX = "_fulltext";
25
26     private final String mTableName;
27     private final ColumnInfo[] mColumnInfo;
28     private final String[] mProjection;
29     private final boolean mHasFullTextIndex;
30
31     public EntrySchema(Class<? extends Entry> clazz) {
32         // Get table and column metadata from reflection.
33         ColumnInfo[] columns = parseColumnInfo(clazz);
34         mTableName = parseTableName(clazz);
35         mColumnInfo = columns;
36
37         // Cache the list of projection columns and check for full-text columns.
38         String[] projection = {};
39         boolean hasFullTextIndex = false;
40         if (columns != null) {
41             projection = new String[columns.length];
42             for (int i = 0; i != columns.length; ++i) {
43                 ColumnInfo column = columns[i];
44                 projection[i] = column.name;
45                 if (column.fullText) {
46                     hasFullTextIndex = true;
47                 }
48             }
49         }
50         mProjection = projection;
51         mHasFullTextIndex = hasFullTextIndex;
52     }
53
54     public String getTableName() {
55         return mTableName;
56     }
57
58     public ColumnInfo[] getColumnInfo() {
59         return mColumnInfo;
60     }
61
62     public String[] getProjection() {
63         return mProjection;
64     }
65
66     private void logExecSql(SQLiteDatabase db, String sql) {
67         // Log.i(TAG, sql);
68         db.execSQL(sql);
69     }
70
71     public void cursorToObject(Cursor cursor, Entry object) {
72         try {
73             ColumnInfo[] columns = mColumnInfo;
74             for (int i = 0, size = columns.length; i != size; ++i) {
75                 ColumnInfo column = columns[i];
76                 int columnIndex = column.projectionIndex;
77                 Field field = column.field;
78                 switch (column.type) {
79                 case TYPE_STRING:
80                     field.set(object, cursor.getString(columnIndex));
81                     break;
82                 case TYPE_BOOLEAN:
83                     field.setBoolean(object, cursor.getShort(columnIndex) == 1);
84                     break;
85                 case TYPE_SHORT:
86                     field.setShort(object, cursor.getShort(columnIndex));
87                     break;
88                 case TYPE_INT:
89                     field.setInt(object, cursor.getInt(columnIndex));
90                     break;
91                 case TYPE_LONG:
92                     field.setLong(object, cursor.getLong(columnIndex));
93                     break;
94                 case TYPE_FLOAT:
95                     field.setFloat(object, cursor.getFloat(columnIndex));
96                     break;
97                 case TYPE_DOUBLE:
98                     field.setDouble(object, cursor.getDouble(columnIndex));
99                     break;
100                 case TYPE_BLOB:
101                     field.set(object, cursor.getBlob(columnIndex));
102                     break;
103                 }
104             }
105         } catch (IllegalArgumentException e) {
106             Log.e(TAG, "SchemaInfo.setFromCursor: object not of the right type");
107         } catch (IllegalAccessException e) {
108             Log.e(TAG, "SchemaInfo.setFromCursor: field not accessible");
109         }
110     }
111
112     public void objectToValues(Entry object, ContentValues values) {
113         try {
114             ColumnInfo[] columns = mColumnInfo;
115             for (int i = 0, size = columns.length; i != size; ++i) {
116                 ColumnInfo column = columns[i];
117                 String columnName = column.name;
118                 Field field = column.field;
119                 switch (column.type) {
120                 case TYPE_STRING:
121                     values.put(columnName, (String) field.get(object));
122                     break;
123                 case TYPE_BOOLEAN:
124                     values.put(columnName, field.getBoolean(object));
125                     break;
126                 case TYPE_SHORT:
127                     values.put(columnName, field.getShort(object));
128                     break;
129                 case TYPE_INT:
130                     values.put(columnName, field.getInt(object));
131                     break;
132                 case TYPE_LONG:
133                     values.put(columnName, field.getLong(object));
134                     break;
135                 case TYPE_FLOAT:
136                     values.put(columnName, field.getFloat(object));
137                     break;
138                 case TYPE_DOUBLE:
139                     values.put(columnName, field.getDouble(object));
140                     break;
141                 case TYPE_BLOB:
142                     values.put(columnName, (byte[]) field.get(object));
143                     break;
144                 }
145             }
146         } catch (IllegalArgumentException e) {
147             Log.e(TAG, "SchemaInfo.setFromCursor: object not of the right type");
148         } catch (IllegalAccessException e) {
149             Log.e(TAG, "SchemaInfo.setFromCursor: field not accessible");
150         }
151     }
152
153     public Cursor queryAll(SQLiteDatabase db) {
154         return db.query(mTableName, mProjection, null, null, null, null, null);
155     }
156
157     public boolean queryWithId(SQLiteDatabase db, long id, Entry entry) {
158         Cursor cursor = db.query(mTableName, mProjection, "_id=?", new String[] { Long.toString(id) }, null, null, null);
159         boolean success = false;
160         if (cursor.moveToFirst()) {
161             cursorToObject(cursor, entry);
162             success = true;
163         }
164         cursor.close();
165         return success;
166     }
167
168     public long insertOrReplace(SQLiteDatabase db, Entry entry) {
169         ContentValues values = new ContentValues();
170         objectToValues(entry, values);
171         if (entry.id == 0) {
172             Log.i(TAG, "removing id before insert");
173             values.remove("_id");
174         }
175         long id = db.replace(mTableName, "_id", values);
176         entry.id = id;
177         return id;
178     }
179
180     public boolean deleteWithId(SQLiteDatabase db, long id) {
181         return db.delete(mTableName, "_id=?", new String[] { Long.toString(id) }) == 1;
182     }
183
184     public void createTables(SQLiteDatabase db) {
185         // Wrapped class must have a @Table.Definition.
186         String tableName = mTableName;
187         if (tableName == null) {
188             return;
189         }
190
191         // Add the CREATE TABLE statement for the main table.
192         StringBuilder sql = new StringBuilder("CREATE TABLE ");
193         sql.append(tableName);
194         sql.append(" (_id INTEGER PRIMARY KEY");
195         ColumnInfo[] columns = mColumnInfo;
196         int numColumns = columns.length;
197         for (int i = 0; i != numColumns; ++i) {
198             ColumnInfo column = columns[i];
199             if (!column.isId()) {
200                 sql.append(',');
201                 sql.append(column.name);
202                 sql.append(' ');
203                 sql.append(SQLITE_TYPES[column.type]);
204                 if (column.extraSql != null) {
205                     sql.append(' ');
206                     sql.append(column.extraSql);
207                 }
208             }
209         }
210         sql.append(");");
211         logExecSql(db, sql.toString());
212         sql.setLength(0);
213
214         // Create indexes for all indexed columns.
215         for (int i = 0; i != numColumns; ++i) {
216             // Create an index on the indexed columns.
217             ColumnInfo column = columns[i];
218             if (column.indexed) {
219                 sql.append("CREATE INDEX ");
220                 sql.append(tableName);
221                 sql.append("_index_");
222                 sql.append(column.name);
223                 sql.append(" ON ");
224                 sql.append(tableName);
225                 sql.append(" (");
226                 sql.append(column.name);
227                 sql.append(");");
228                 logExecSql(db, sql.toString());
229                 sql.setLength(0);
230             }
231         }
232
233         if (mHasFullTextIndex) {
234             // Add an FTS virtual table if using full-text search.
235             String ftsTableName = tableName + FULL_TEXT_INDEX_SUFFIX;
236             sql.append("CREATE VIRTUAL TABLE ");
237             sql.append(ftsTableName);
238             sql.append(" USING FTS3 (_id INTEGER PRIMARY KEY");
239             for (int i = 0; i != numColumns; ++i) {
240                 ColumnInfo column = columns[i];
241                 if (column.fullText) {
242                     // Add the column to the FTS table.
243                     String columnName = column.name;
244                     sql.append(',');
245                     sql.append(columnName);
246                     sql.append(" TEXT");
247                 }
248             }
249             sql.append(");");
250             logExecSql(db, sql.toString());
251             sql.setLength(0);
252
253             // Build an insert statement that will automatically keep the FTS
254             // table in sync.
255             StringBuilder insertSql = new StringBuilder("INSERT OR REPLACE INTO ");
256             insertSql.append(ftsTableName);
257             insertSql.append(" (_id");
258             for (int i = 0; i != numColumns; ++i) {
259                 ColumnInfo column = columns[i];
260                 if (column.fullText) {
261                     insertSql.append(',');
262                     insertSql.append(column.name);
263                 }
264             }
265             insertSql.append(") VALUES (new._id");
266             for (int i = 0; i != numColumns; ++i) {
267                 ColumnInfo column = columns[i];
268                 if (column.fullText) {
269                     insertSql.append(",new.");
270                     insertSql.append(column.name);
271                 }
272             }
273             insertSql.append(");");
274             String insertSqlString = insertSql.toString();
275
276             // Add an insert trigger.
277             sql.append("CREATE TRIGGER ");
278             sql.append(tableName);
279             sql.append("_insert_trigger AFTER INSERT ON ");
280             sql.append(tableName);
281             sql.append(" FOR EACH ROW BEGIN ");
282             sql.append(insertSqlString);
283             sql.append("END;");
284             logExecSql(db, sql.toString());
285             sql.setLength(0);
286
287             // Add an update trigger.
288             sql.append("CREATE TRIGGER ");
289             sql.append(tableName);
290             sql.append("_update_trigger AFTER UPDATE ON ");
291             sql.append(tableName);
292             sql.append(" FOR EACH ROW BEGIN ");
293             sql.append(insertSqlString);
294             sql.append("END;");
295             logExecSql(db, sql.toString());
296             sql.setLength(0);
297
298             // Add a delete trigger.
299             sql.append("CREATE TRIGGER ");
300             sql.append(tableName);
301             sql.append("_delete_trigger AFTER DELETE ON ");
302             sql.append(tableName);
303             sql.append(" FOR EACH ROW BEGIN DELETE FROM ");
304             sql.append(ftsTableName);
305             sql.append(" WHERE _id = old._id; END;");
306             logExecSql(db, sql.toString());
307             sql.setLength(0);
308         }
309     }
310
311     public void dropTables(SQLiteDatabase db) {
312         String tableName = mTableName;
313         StringBuilder sql = new StringBuilder("DROP TABLE IF EXISTS ");
314         sql.append(tableName);
315         sql.append(';');
316         logExecSql(db, sql.toString());
317         sql.setLength(0);
318
319         if (mHasFullTextIndex) {
320             sql.append("DROP TABLE IF EXISTS ");
321             sql.append(tableName);
322             sql.append(FULL_TEXT_INDEX_SUFFIX);
323             sql.append(';');
324             logExecSql(db, sql.toString());
325         }
326
327     }
328
329     public void deleteAll(SQLiteDatabase db) {
330         StringBuilder sql = new StringBuilder("DELETE FROM ");
331         sql.append(mTableName);
332         sql.append(";");
333         logExecSql(db, sql.toString());
334     }
335
336     private String parseTableName(Class<? extends Object> clazz) {
337         // Check for a table annotation.
338         Entry.Table table = clazz.getAnnotation(Entry.Table.class);
339         if (table == null) {
340             return null;
341         }
342
343         // Return the table name.
344         return table.value();
345     }
346
347     private ColumnInfo[] parseColumnInfo(Class<? extends Object> clazz) {
348         // Gather metadata from each annotated field.
349         ArrayList<ColumnInfo> columns = new ArrayList<ColumnInfo>();
350         Field[] fields = clazz.getFields();
351         for (int i = 0; i != fields.length; ++i) {
352             // Get column metadata from the annotation.
353             Field field = fields[i];
354             Entry.Column info = ((AnnotatedElement) field).getAnnotation(Entry.Column.class);
355             if (info == null) {
356                 continue;
357             }
358
359             // Determine the field type.
360             int type;
361             Class<?> fieldType = field.getType();
362             if (fieldType == String.class) {
363                 type = TYPE_STRING;
364             } else if (fieldType == boolean.class) {
365                 type = TYPE_BOOLEAN;
366             } else if (fieldType == short.class) {
367                 type = TYPE_SHORT;
368             } else if (fieldType == int.class) {
369                 type = TYPE_INT;
370             } else if (fieldType == long.class) {
371                 type = TYPE_LONG;
372             } else if (fieldType == float.class) {
373                 type = TYPE_FLOAT;
374             } else if (fieldType == double.class) {
375                 type = TYPE_DOUBLE;
376             } else if (fieldType == byte[].class) {
377                 type = TYPE_BLOB;
378             } else {
379                 throw new IllegalArgumentException("Unsupported field type for column: " + fieldType.getName());
380             }
381
382             // Add the column to the array.
383             int index = columns.size();
384             columns.add(new ColumnInfo(info.value(), type, info.indexed(), info.fullText(), field, index));
385         }
386
387         // Return a list.
388         ColumnInfo[] columnList = new ColumnInfo[columns.size()];
389         columns.toArray(columnList);
390         return columnList;
391     }
392
393     public static final class ColumnInfo {
394         public final String name;
395         public final int type;
396         public final boolean indexed;
397         public final boolean fullText;
398         public final String extraSql = "";
399         public final Field field;
400         public final int projectionIndex;
401
402         public ColumnInfo(String name, int type, boolean indexed, boolean fullText, Field field, int projectionIndex) {
403             this.name = name.toLowerCase();
404             this.type = type;
405             this.indexed = indexed;
406             this.fullText = fullText;
407             this.field = field;
408             this.projectionIndex = projectionIndex;
409         }
410
411         public boolean isId() {
412             return name == "_id";
413         }
414     }
415 }