├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── libraries
│ ├── Maven__com_google_code_gson_gson_2_2_4.xml
│ ├── Maven__commons_collections_commons_collections_3_2_1.xml
│ ├── Maven__commons_lang_commons_lang_2_4.xml
│ └── Maven__org_apache_velocity_velocity_1_7.xml
├── misc.xml
├── modules.xml
├── scopes
│ └── scope_settings.xml
├── uiDesigner.xml
└── vcs.xml
├── ClientTest
├── ClientTest.iml
├── libs
│ └── gson-2.2.4.jar
├── resources
│ ├── contentProviderSchema.json
│ └── contentProviderSchema_simple.json
├── scratch
│ └── RainEmployeeProvider_many_many.java
└── src
│ └── com
│ └── rain
│ └── utils
│ └── android
│ └── robocop
│ └── clienttest
│ └── Main.java
├── README.md
├── RoboCoP.iml
├── libs
└── gson-2.2.4.jar
├── pom.xml
└── src
└── main
├── java
└── com
│ └── rain
│ └── utils
│ └── android
│ └── robocop
│ ├── generator
│ ├── ContentProviderGenerator.java
│ └── ContentProviderWriter.java
│ └── model
│ ├── ContentProviderModel.java
│ ├── ContentProviderRelationshipModel.java
│ ├── ContentProviderTableModel.java
│ └── StringUtils.java
└── resources
├── ContentProvider.vm
├── Database.vm
├── META-INF
└── services
│ └── javax.annotation.processing.Processor
├── Model.vm
├── ProviderXML.vm
├── Table.vm
└── velocity.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | ClientTest/src-gen
2 | target
3 | out
4 | workspace.xml
5 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | ContentProviderGenerator
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__com_google_code_gson_gson_2_2_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__commons_collections_commons_collections_3_2_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__commons_lang_commons_lang_2_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_apache_velocity_velocity_1_7.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ClientTest/ClientTest.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ClientTest/libs/gson-2.2.4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mediarain/RoboCoP/789c52c9e9f68b4fbb40086bc360741af5117a8d/ClientTest/libs/gson-2.2.4.jar
--------------------------------------------------------------------------------
/ClientTest/resources/contentProviderSchema.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageName": "com.ifit.android.sdk",
3 | "providerName": "ifit_database",
4 | "databaseVersion": 26,
5 | "tables": [
6 | {
7 | "name": "FeaturedWorkout",
8 | "members" : [
9 | {
10 | "type" : "String",
11 | "name" : "feature_title"
12 | },
13 | {
14 | "type" : "String",
15 | "name" : "feature_tag_line"
16 | },
17 | {
18 | "type" : "String",
19 | "name" : "promo_image_path"
20 | },
21 | {
22 | "type" : "String",
23 | "name" : "workout_id"
24 | },
25 | {
26 | "type" : "double",
27 | "name" : "distance"
28 | },
29 | {
30 | "type" : "int",
31 | "name" : "calories"
32 | },
33 | {
34 | "type" : "double",
35 | "name" : "maxincline"
36 | }
37 | ]
38 | },
39 | {
40 | "name": "Workout",
41 | "members": [
42 | {
43 | "type": "String",
44 | "name": "workout_title"
45 | },
46 | {
47 | "type": "String",
48 | "name": "date_created"
49 | },
50 | {
51 | "type": "String",
52 | "name": "description"
53 | },
54 | {
55 | "type": "String",
56 | "name": "workout_type"
57 | },
58 | {
59 | "type": "float",
60 | "name": "distance"
61 | },
62 | {
63 | "type": "float",
64 | "name": "max_incline"
65 | },
66 | {
67 | "type": "int",
68 | "name": "calories"
69 | },
70 | {
71 | "type": "String",
72 | "name": "workout_id"
73 | },
74 | {
75 | "type": "String",
76 | "name": "wpl_path"
77 | },
78 | {
79 | "type": "String",
80 | "name": "image_url"
81 | },
82 | {
83 | "type": "String",
84 | "name": "date_scheduled"
85 | },
86 | {
87 | "type": "String",
88 | "name": "workout_collection_id"
89 | },
90 | {
91 | "type": "String",
92 | "name": "workout_event_id"
93 | },
94 | {
95 | "type": "String",
96 | "name": "workout_user_id"
97 | },
98 | {
99 | "type": "String",
100 | "name": "route_id"
101 | },
102 | {
103 | "type": "String",
104 | "name": "date_workout_end"
105 | },
106 | {
107 | "type": "int",
108 | "name": "all_day"
109 | },
110 | {
111 | "type": "double",
112 | "name": "target_value"
113 | },
114 | {
115 | "type": "String",
116 | "name": "target_type"
117 | },
118 | {
119 | "type": "String",
120 | "name": "controls"
121 | },
122 | {
123 | "type": "double",
124 | "name": "elevation_gained"
125 | },
126 | {
127 | "type": "double",
128 | "name": "elevation_loss"
129 | },
130 | {
131 | "type": "int",
132 | "name": "duration"
133 | }
134 | ]
135 | },
136 | {
137 | "name": "WorkoutCollection",
138 | "members": [
139 | {
140 | "type": "String",
141 | "name": "workout_collection_title"
142 | },
143 | {
144 | "type": "String",
145 | "name": "workout_collection_description"
146 | },
147 | {
148 | "type": "String",
149 | "name": "workout_collection_thumbnail_path"
150 | },
151 | {
152 | "type": "boolean",
153 | "name": "is_added"
154 | },
155 | {
156 | "type": "String",
157 | "name": "workout_collection_type"
158 | },
159 | {
160 | "type": "String",
161 | "name": "workout_collection_type_extra"
162 | },
163 | {
164 | "type": "String",
165 | "name": "external_id"
166 | },
167 | {
168 | "type": "boolean",
169 | "name": "is_adhoc"
170 | },
171 | {
172 | "type": "boolean",
173 | "name": "is_visible"
174 | },
175 | {
176 | "type": "String",
177 | "name": "collection_user_id"
178 | }
179 | ]
180 | },
181 | {
182 | "name": "WorkoutHistory",
183 | "members": [
184 | {
185 | "type": "String",
186 | "name": "workout_session_id"
187 | },
188 | {
189 | "type": "String",
190 | "name": "user_id"
191 | },
192 | {
193 | "type": "boolean",
194 | "name": "finished"
195 | },
196 | {
197 | "type": "String",
198 | "name": "workout_id"
199 | },
200 | {
201 | "type": "String",
202 | "name": "route_id"
203 | },
204 | {
205 | "type": "String",
206 | "name": "start_time"
207 | },
208 | {
209 | "type": "String",
210 | "name": "end_time"
211 | },
212 | {
213 | "type": "String",
214 | "name": "notes"
215 | },
216 | {
217 | "type": "boolean",
218 | "name": "complete_challenge"
219 | },
220 | {
221 | "type": "double",
222 | "name": "calories"
223 | },
224 | {
225 | "type": "double",
226 | "name": "average_heart_rate"
227 | },
228 | {
229 | "type": "double",
230 | "name": "max_heart_rate"
231 | },
232 | {
233 | "type": "double",
234 | "name": "distance"
235 | },
236 | {
237 | "type": "double",
238 | "name": "duration"
239 | },
240 | {
241 | "type": "String",
242 | "name": "type"
243 | },
244 | {
245 | "type": "String",
246 | "name": "timestamp"
247 | },
248 | {
249 | "type": "boolean",
250 | "name": "manually_logged"
251 | },
252 | {
253 | "type": "String",
254 | "name": "title"
255 | },
256 | {
257 | "type": "int",
258 | "name": "page_num"
259 | },
260 | {
261 | "type": "String",
262 | "name": "workout_instance_id"
263 | },
264 | {
265 | "type": "double",
266 | "name": "elevation_gain"
267 | },
268 | {
269 | "type": "double",
270 | "name": "elevation_loss"
271 | },
272 | {
273 | "type": "String",
274 | "name": "description"
275 | },
276 | {
277 | "type": "String",
278 | "name": "stats"
279 | }
280 | ]
281 | },
282 | {
283 | "name": "WorkoutRoute",
284 | "members": [
285 | {
286 | "type": "String",
287 | "name": "workout_id"
288 | },
289 | {
290 | "type": "double",
291 | "name": "average_grade"
292 | },
293 | {
294 | "type": "double",
295 | "name": "total_elevation_loss"
296 | },
297 | {
298 | "type": "double",
299 | "name": "total_elevation_gain"
300 | },
301 | {
302 | "type": "double",
303 | "name": "distance"
304 | },
305 | {
306 | "type": "double",
307 | "name": "end_lat"
308 | },
309 | {
310 | "type": "double",
311 | "name": "end_lon"
312 | },
313 | {
314 | "type": "double",
315 | "name": "start_lat"
316 | },
317 | {
318 | "type": "double",
319 | "name": "start_lon"
320 | },
321 | {
322 | "type": "String",
323 | "name": "route_id"
324 | },
325 | {
326 | "type": "String",
327 | "name": "date"
328 | },
329 | {
330 | "type": "String",
331 | "name": "path_data"
332 | }
333 | ]
334 | },
335 | {
336 | "name": "WorkoutWorkoutCollection",
337 | "members": [
338 | {
339 | "type": "long",
340 | "name": "workout_id"
341 | },
342 | {
343 | "type": "long",
344 | "name": "workout_collection_id"
345 | }
346 | ]
347 | },
348 | {
349 | "name": "UserWeight",
350 | "members": [
351 | {
352 | "type": "String",
353 | "name": "user_id"
354 | },
355 | {
356 | "type": "double",
357 | "name": "kilograms"
358 | },
359 | {
360 | "type": "String",
361 | "name": "notes"
362 | },
363 | {
364 | "type": "String",
365 | "name": "remote_id"
366 | },
367 | {
368 | "type": "long",
369 | "name": "date"
370 | },
371 | {
372 | "type": "long",
373 | "name": "timestamp"
374 | }
375 | ]
376 | },
377 | {
378 | "name": "MealItem",
379 | "members": [
380 | {
381 | "type": "String",
382 | "name": "user_id"
383 | },
384 | {
385 | "type": "long",
386 | "name": "date"
387 | },
388 | {
389 | "type": "long",
390 | "name": "food_id"
391 | },
392 | {
393 | "type": "String",
394 | "name": "meal"
395 | },
396 | {
397 | "type": "long",
398 | "name": "serving_id"
399 | },
400 | {
401 | "type": "String",
402 | "name": "remote_id"
403 | },
404 | {
405 | "type": "double",
406 | "name": "servings"
407 | },
408 | {
409 | "type": "long",
410 | "name": "timestamp"
411 | }
412 | ]
413 | },
414 | {
415 | "name": "FoodDetail",
416 | "members": [
417 | {
418 | "type": "String",
419 | "name": "food_id"
420 | },
421 | {
422 | "type": "String",
423 | "name": "food_name"
424 | },
425 | {
426 | "type": "String",
427 | "name": "food_type"
428 | },
429 | {
430 | "type": "String",
431 | "name": "food_url"
432 | },
433 | {
434 | "type": "String",
435 | "name": "servings"
436 | },
437 | {
438 | "type": "String",
439 | "name": "img"
440 | }
441 | ]
442 | }
443 | ],
444 | "relationships": []
445 | }
446 |
--------------------------------------------------------------------------------
/ClientTest/resources/contentProviderSchema_simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageName": "com.rain.example.data",
3 | "providerName": "rain_employee",
4 | "databaseVersion": 1,
5 | "tables": [
6 | {
7 | "name": "person",
8 | "members" : [
9 | {
10 | "type" : "string",
11 | "name" : "name"
12 | },
13 | {
14 | "type" : "int",
15 | "name" : "age"
16 | },
17 | ]
18 | },
19 | {
20 | "name": "hobby",
21 | "members": [
22 | {
23 | "type": "string",
24 | "name": "name"
25 | },
26 | {
27 | "type": "string",
28 | "name": "description"
29 | }
30 | ]
31 | },
32 | {
33 | "name": "phone_contact",
34 | "members": [
35 | {
36 | "type": "string",
37 | "name": "number"
38 | },
39 | {
40 | "type": "string",
41 | "name": "phone_type"
42 | }
43 | ]
44 | },
45 | {
46 | "name": "work_place",
47 | "members": [
48 | {
49 | "type": "string",
50 | "name": "name"
51 | },
52 | {
53 | "type": "string",
54 | "name": "address"
55 | }
56 | ]
57 | },
58 | {
59 | "name": "vehicle",
60 | "members": [
61 | {
62 | "type": "string",
63 | "name": "make"
64 | },
65 | {
66 | "type": "string",
67 | "name": "model"
68 | },
69 | {
70 | "type": "string",
71 | "name": "color"
72 | }
73 | ]
74 | }
75 | ],
76 | "relationships" : [
77 | {
78 | "name" : "person_vehicle",
79 | "left_table" : "person",
80 | "right_table" : "vehicle",
81 | "type" : "to_many"
82 | }
83 | ]
84 | }
85 |
--------------------------------------------------------------------------------
/ClientTest/scratch/RainEmployeeProvider_many_many.java:
--------------------------------------------------------------------------------
1 | package com.museami.android.hookd.provider;
2 |
3 | import com.museami.android.hookd.database.HookdDatabase;
4 |
5 | import com.museami.android.hookd.database.table.*;
6 |
7 | import android.provider.BaseColumns;
8 | import android.text.TextUtils;
9 | import android.content.ContentUris;
10 | import android.database.sqlite.SQLiteQueryBuilder;
11 |
12 | import android.content.ContentProvider;
13 | import android.content.ContentValues;
14 | import android.content.UriMatcher;
15 | import android.database.Cursor;
16 | import android.database.sqlite.SQLiteDatabase;
17 | import android.net.Uri;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | public class HookdProvider extends ContentProvider {
23 | public static final String AUTHORITY = "com.museami.android.hookd.provider.hookd";
24 | public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
25 |
26 | public static final Uri YOUTUBEVIDEO_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, YoutubeVideoContent.CONTENT_PATH);
27 |
28 | public static final Uri YOUTUBECOMMENT_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, YoutubeCommentContent.CONTENT_PATH);
29 |
30 | public static final Uri ARTIST_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, ArtistContent.CONTENT_PATH);
31 |
32 | public static final Uri SONG_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, SongContent.CONTENT_PATH);
33 |
34 | public static final Uri RECORDEDVIDEO_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, RecordedVideoContent.CONTENT_PATH);
35 |
36 | public static final Uri YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, YoutubeVideoJoinYoutubeCommentContent.CONTENT_PATH);
37 |
38 | public static final Uri ARTIST_JOIN_SONG_CONTENT_URI = Uri.withAppendedPath(HookdProvider.AUTHORITY_URI, ArtistJoinSongContent.CONTENT_PATH);
39 |
40 | private static final UriMatcher URI_MATCHER;
41 | private HookdDatabase mDatabase;
42 |
43 | private static final int YOUTUBEVIDEO_DIR = 0;
44 | private static final int YOUTUBEVIDEO_ID = 1;
45 |
46 | private static final int YOUTUBECOMMENT_DIR = 2;
47 | private static final int YOUTUBECOMMENT_ID = 3;
48 |
49 | private static final int ARTIST_DIR = 4;
50 | private static final int ARTIST_ID = 5;
51 |
52 | private static final int SONG_DIR = 6;
53 | private static final int SONG_ID = 7;
54 |
55 | private static final int RECORDEDVIDEO_DIR = 8;
56 | private static final int RECORDEDVIDEO_ID = 9;
57 |
58 | private static final int YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_DIR = 10;
59 |
60 | private static final int ARTIST_JOIN_SONG_DIR = 12;
61 |
62 | static {
63 | URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
64 |
65 | URI_MATCHER.addURI(AUTHORITY, YoutubeVideoContent.CONTENT_PATH, YOUTUBEVIDEO_DIR);
66 | URI_MATCHER.addURI(AUTHORITY, YoutubeVideoContent.CONTENT_PATH + "/#", YOUTUBEVIDEO_ID);
67 |
68 | URI_MATCHER.addURI(AUTHORITY, YoutubeCommentContent.CONTENT_PATH, YOUTUBECOMMENT_DIR);
69 | URI_MATCHER.addURI(AUTHORITY, YoutubeCommentContent.CONTENT_PATH + "/#", YOUTUBECOMMENT_ID);
70 |
71 | URI_MATCHER.addURI(AUTHORITY, ArtistContent.CONTENT_PATH, ARTIST_DIR);
72 | URI_MATCHER.addURI(AUTHORITY, ArtistContent.CONTENT_PATH + "/#", ARTIST_ID);
73 |
74 | URI_MATCHER.addURI(AUTHORITY, SongContent.CONTENT_PATH, SONG_DIR);
75 | URI_MATCHER.addURI(AUTHORITY, SongContent.CONTENT_PATH + "/#", SONG_ID);
76 |
77 | URI_MATCHER.addURI(AUTHORITY, RecordedVideoContent.CONTENT_PATH, RECORDEDVIDEO_DIR);
78 | URI_MATCHER.addURI(AUTHORITY, RecordedVideoContent.CONTENT_PATH + "/#", RECORDEDVIDEO_ID);
79 |
80 | URI_MATCHER.addURI(AUTHORITY, YoutubeVideoJoinYoutubeCommentContent.CONTENT_PATH, YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_DIR);
81 |
82 | URI_MATCHER.addURI(AUTHORITY, ArtistJoinSongContent.CONTENT_PATH, ARTIST_JOIN_SONG_DIR);
83 | }
84 |
85 | public static final class YoutubeVideoContent implements BaseColumns {
86 | public static final String CONTENT_PATH = "youtubevideo";
87 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.youtubevideo";
88 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hookd.youtubevideo";
89 | }
90 |
91 | public static final class YoutubeCommentContent implements BaseColumns {
92 | public static final String CONTENT_PATH = "youtubecomment";
93 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.youtubecomment";
94 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hookd.youtubecomment";
95 | }
96 |
97 | public static final class ArtistContent implements BaseColumns {
98 | public static final String CONTENT_PATH = "artist";
99 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.artist";
100 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hookd.artist";
101 | }
102 |
103 | public static final class SongContent implements BaseColumns {
104 | public static final String CONTENT_PATH = "song";
105 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.song";
106 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hookd.song";
107 | }
108 |
109 | public static final class RecordedVideoContent implements BaseColumns {
110 | public static final String CONTENT_PATH = "recordedvideo";
111 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.recordedvideo";
112 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hookd.recordedvideo";
113 | }
114 |
115 | public static final class YoutubeVideoJoinYoutubeCommentContent implements BaseColumns {
116 | public static final String CONTENT_PATH = "youtubevideo_join_youtubecomment";
117 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.youtubevideo_join_youtubecomment";
118 | }
119 |
120 | public static final class ArtistJoinSongContent implements BaseColumns {
121 | public static final String CONTENT_PATH = "artist_join_song";
122 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hookd.artist_join_song";
123 | }
124 |
125 | @Override
126 | public final boolean onCreate() {
127 | mDatabase = new HookdDatabase(getContext());
128 | return true;
129 | }
130 |
131 | @Override
132 | public final String getType(final Uri uri) {
133 | switch (URI_MATCHER.match(uri)) {
134 | case YOUTUBEVIDEO_DIR:
135 | return YoutubeVideoContent.CONTENT_TYPE;
136 | case YOUTUBEVIDEO_ID:
137 | return YoutubeVideoContent.CONTENT_ITEM_TYPE;
138 |
139 | case YOUTUBECOMMENT_DIR:
140 | return YoutubeCommentContent.CONTENT_TYPE;
141 | case YOUTUBECOMMENT_ID:
142 | return YoutubeCommentContent.CONTENT_ITEM_TYPE;
143 |
144 | case ARTIST_DIR:
145 | return ArtistContent.CONTENT_TYPE;
146 | case ARTIST_ID:
147 | return ArtistContent.CONTENT_ITEM_TYPE;
148 |
149 | case SONG_DIR:
150 | return SongContent.CONTENT_TYPE;
151 | case SONG_ID:
152 | return SongContent.CONTENT_ITEM_TYPE;
153 |
154 | case RECORDEDVIDEO_DIR:
155 | return RecordedVideoContent.CONTENT_TYPE;
156 | case RECORDEDVIDEO_ID:
157 | return RecordedVideoContent.CONTENT_ITEM_TYPE;
158 |
159 | case YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_DIR:
160 | return YoutubeVideoJoinYoutubeCommentContent.CONTENT_TYPE;
161 |
162 | case ARTIST_JOIN_SONG_DIR:
163 | return ArtistJoinSongContent.CONTENT_TYPE;
164 |
165 | default:
166 | throw new IllegalArgumentException("Unsupported URI: " + uri);
167 | }
168 | }
169 |
170 | @Override
171 | public final Cursor query(final Uri uri, String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) {
172 | final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
173 | final SQLiteDatabase dbConnection = mDatabase.getReadableDatabase();
174 |
175 | switch (URI_MATCHER.match(uri)) {
176 | case YOUTUBEVIDEO_ID:
177 | queryBuilder.appendWhere(YoutubeVideoTable._ID + "=" + uri.getLastPathSegment());
178 | case YOUTUBEVIDEO_DIR:
179 | queryBuilder.setTables(YoutubeVideoTable.TABLE_NAME);
180 | break;
181 |
182 | case YOUTUBECOMMENT_ID:
183 | queryBuilder.appendWhere(YoutubeCommentTable._ID + "=" + uri.getLastPathSegment());
184 | case YOUTUBECOMMENT_DIR:
185 | queryBuilder.setTables(YoutubeCommentTable.TABLE_NAME);
186 | break;
187 |
188 | case ARTIST_ID:
189 | queryBuilder.appendWhere(ArtistTable._ID + "=" + uri.getLastPathSegment());
190 | case ARTIST_DIR:
191 | queryBuilder.setTables(ArtistTable.TABLE_NAME);
192 | break;
193 |
194 | case SONG_ID:
195 | queryBuilder.appendWhere(SongTable._ID + "=" + uri.getLastPathSegment());
196 | case SONG_DIR:
197 | queryBuilder.setTables(SongTable.TABLE_NAME);
198 | break;
199 |
200 | case RECORDEDVIDEO_ID:
201 | queryBuilder.appendWhere(RecordedVideoTable._ID + "=" + uri.getLastPathSegment());
202 | case RECORDEDVIDEO_DIR:
203 | queryBuilder.setTables(RecordedVideoTable.TABLE_NAME);
204 | break;
205 |
206 | case YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_DIR:
207 | queryBuilder.setTables(YoutubeVideoTable.TABLE_NAME + " LEFT OUTER JOIN " + YoutubeCommentTable.TABLE_NAME + " ON (" + YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.ID + "=" + YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.VIDEO_ID + ")");
208 |
209 | projection = new String[] {
210 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable._ID + " || " + YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable._ID + " AS " + YoutubeVideoTable._ID,
211 |
212 | YoutubeVideoTable.TABLE_NAME + "._id AS " + YoutubeVideoTable.TABLE_NAME + "__id",
213 |
214 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.ID + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.ID,
215 |
216 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.PUBLISHED_TIME + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.PUBLISHED_TIME,
217 |
218 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.TITLE + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.TITLE,
219 |
220 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.DESCRIPTION + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.DESCRIPTION,
221 |
222 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.UPLOADER_CHANNEL_ID + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.UPLOADER_CHANNEL_ID,
223 |
224 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.UPLOADER + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.UPLOADER,
225 |
226 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.UPLOADER_THUMBNAIL + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.UPLOADER_THUMBNAIL,
227 |
228 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.VIEWS + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.VIEWS,
229 |
230 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.LIKES + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.LIKES,
231 |
232 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.THUMBNAIL_URL + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.THUMBNAIL_URL,
233 |
234 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.LIKED + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.LIKED,
235 |
236 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.HAS_COMMENTS + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.HAS_COMMENTS,
237 |
238 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.COMMENT_COUNT + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.COMMENT_COUNT,
239 |
240 | YoutubeVideoTable.TABLE_NAME + "." + YoutubeVideoTable.STREAM_TYPE + " AS " + YoutubeVideoTable.TABLE_NAME + "_" + YoutubeVideoTable.STREAM_TYPE,
241 |
242 | YoutubeCommentTable.TABLE_NAME + "._id AS " + YoutubeCommentTable.TABLE_NAME + "__id",
243 |
244 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.VIDEO_ID + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.VIDEO_ID,
245 |
246 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.PUBLISHED_TIME + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.PUBLISHED_TIME,
247 |
248 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.AUTHOR_CHANNEL_ID + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.AUTHOR_CHANNEL_ID,
249 |
250 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.AUTHOR + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.AUTHOR,
251 |
252 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.AUTHOR_THUMBNAIL + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.AUTHOR_THUMBNAIL,
253 |
254 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.COMMENT + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.COMMENT,
255 |
256 | YoutubeCommentTable.TABLE_NAME + "." + YoutubeCommentTable.TOP_COMMENT + " AS " + YoutubeCommentTable.TABLE_NAME + "_" + YoutubeCommentTable.TOP_COMMENT,
257 | };
258 | break;
259 |
260 | case ARTIST_JOIN_SONG_DIR:
261 | queryBuilder.setTables(ArtistTable.TABLE_NAME + " LEFT OUTER JOIN " + SongTable.TABLE_NAME + " ON (" + ArtistTable.TABLE_NAME + "." + ArtistTable.NAME + "=" + SongTable.TABLE_NAME + "." + SongTable.ARTIST_NAME + ")");
262 |
263 | projection = new String[] {
264 | ArtistTable.TABLE_NAME + "." + ArtistTable._ID + " || " + SongTable.TABLE_NAME + "." + SongTable._ID + " AS " + ArtistTable._ID,
265 |
266 | ArtistTable.TABLE_NAME + "._id AS " + ArtistTable.TABLE_NAME + "__id",
267 |
268 | ArtistTable.TABLE_NAME + "." + ArtistTable.NAME + " AS " + ArtistTable.TABLE_NAME + "_" + ArtistTable.NAME,
269 |
270 | ArtistTable.TABLE_NAME + "." + ArtistTable.ALBUM_ARTWORK_URI + " AS " + ArtistTable.TABLE_NAME + "_" + ArtistTable.ALBUM_ARTWORK_URI,
271 |
272 | ArtistTable.TABLE_NAME + "." + ArtistTable.BLURRED_ALBUM_ARTWORK_URI + " AS " + ArtistTable.TABLE_NAME + "_" + ArtistTable.BLURRED_ALBUM_ARTWORK_URI,
273 |
274 | ArtistTable.TABLE_NAME + "." + ArtistTable.ARTIST_LOGO_URI + " AS " + ArtistTable.TABLE_NAME + "_" + ArtistTable.ARTIST_LOGO_URI,
275 |
276 | ArtistTable.TABLE_NAME + "." + ArtistTable.ARTIST_SPLASH_URI + " AS " + ArtistTable.TABLE_NAME + "_" + ArtistTable.ARTIST_SPLASH_URI,
277 |
278 | ArtistTable.TABLE_NAME + "." + ArtistTable.BLURRED_ARTIST_SPLASH_URI + " AS " + ArtistTable.TABLE_NAME + "_" + ArtistTable.BLURRED_ARTIST_SPLASH_URI,
279 |
280 | SongTable.TABLE_NAME + "._id AS " + SongTable.TABLE_NAME + "__id",
281 |
282 | SongTable.TABLE_NAME + "." + SongTable.ARTIST_NAME + " AS " + SongTable.TABLE_NAME + "_" + SongTable.ARTIST_NAME,
283 |
284 | SongTable.TABLE_NAME + "." + SongTable.TITLE + " AS " + SongTable.TABLE_NAME + "_" + SongTable.TITLE,
285 |
286 | SongTable.TABLE_NAME + "." + SongTable.MUSIC_URI + " AS " + SongTable.TABLE_NAME + "_" + SongTable.MUSIC_URI,
287 |
288 | SongTable.TABLE_NAME + "." + SongTable.VOCALS_URI + " AS " + SongTable.TABLE_NAME + "_" + SongTable.VOCALS_URI,
289 |
290 | SongTable.TABLE_NAME + "." + SongTable.LYRICS_URI + " AS " + SongTable.TABLE_NAME + "_" + SongTable.LYRICS_URI,
291 |
292 | SongTable.TABLE_NAME + "." + SongTable.CONFIG_URI + " AS " + SongTable.TABLE_NAME + "_" + SongTable.CONFIG_URI,
293 |
294 | SongTable.TABLE_NAME + "." + SongTable.LOCKED + " AS " + SongTable.TABLE_NAME + "_" + SongTable.LOCKED,
295 | };
296 | break;
297 |
298 | default :
299 | throw new IllegalArgumentException("Unsupported URI:" + uri);
300 | }
301 |
302 | Cursor cursor = queryBuilder.query(dbConnection, projection, selection, selectionArgs, null, null, sortOrder);
303 | cursor.setNotificationUri(getContext().getContentResolver(), uri);
304 |
305 | return cursor;
306 |
307 | }
308 |
309 | @Override
310 | public final Uri insert(final Uri uri, final ContentValues values) {
311 | final SQLiteDatabase dbConnection = mDatabase.getWritableDatabase();
312 |
313 | try {
314 | dbConnection.beginTransaction();
315 |
316 | switch (URI_MATCHER.match(uri)) {
317 | case YOUTUBEVIDEO_DIR:
318 | case YOUTUBEVIDEO_ID:
319 | final long youtubevideoId = dbConnection.insertOrThrow(YoutubeVideoTable.TABLE_NAME, null, values);
320 | final Uri newYoutubeVideoUri = ContentUris.withAppendedId(YOUTUBEVIDEO_CONTENT_URI, youtubevideoId);
321 | getContext().getContentResolver().notifyChange(newYoutubeVideoUri, null);
322 | getContext().getContentResolver().notifyChange(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI, null);
323 |
324 | dbConnection.setTransactionSuccessful();
325 | return newYoutubeVideoUri;
326 |
327 | case YOUTUBECOMMENT_DIR:
328 | case YOUTUBECOMMENT_ID:
329 | final long youtubecommentId = dbConnection.insertOrThrow(YoutubeCommentTable.TABLE_NAME, null, values);
330 | final Uri newYoutubeCommentUri = ContentUris.withAppendedId(YOUTUBECOMMENT_CONTENT_URI, youtubecommentId);
331 | getContext().getContentResolver().notifyChange(newYoutubeCommentUri, null);
332 | getContext().getContentResolver().notifyChange(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI, null);
333 |
334 | dbConnection.setTransactionSuccessful();
335 | return newYoutubeCommentUri;
336 |
337 | case ARTIST_DIR:
338 | case ARTIST_ID:
339 | final long artistId = dbConnection.insertOrThrow(ArtistTable.TABLE_NAME, null, values);
340 | final Uri newArtistUri = ContentUris.withAppendedId(ARTIST_CONTENT_URI, artistId);
341 | getContext().getContentResolver().notifyChange(newArtistUri, null);
342 | getContext().getContentResolver().notifyChange(ARTIST_JOIN_SONG_CONTENT_URI, null);
343 |
344 | dbConnection.setTransactionSuccessful();
345 | return newArtistUri;
346 |
347 | case SONG_DIR:
348 | case SONG_ID:
349 | final long songId = dbConnection.insertOrThrow(SongTable.TABLE_NAME, null, values);
350 | final Uri newSongUri = ContentUris.withAppendedId(SONG_CONTENT_URI, songId);
351 | getContext().getContentResolver().notifyChange(newSongUri, null);
352 | getContext().getContentResolver().notifyChange(ARTIST_JOIN_SONG_CONTENT_URI, null);
353 |
354 | dbConnection.setTransactionSuccessful();
355 | return newSongUri;
356 |
357 | case RECORDEDVIDEO_DIR:
358 | case RECORDEDVIDEO_ID:
359 | final long recordedvideoId = dbConnection.insertOrThrow(RecordedVideoTable.TABLE_NAME, null, values);
360 | final Uri newRecordedVideoUri = ContentUris.withAppendedId(RECORDEDVIDEO_CONTENT_URI, recordedvideoId);
361 | getContext().getContentResolver().notifyChange(newRecordedVideoUri, null);
362 | dbConnection.setTransactionSuccessful();
363 | return newRecordedVideoUri;
364 |
365 | default :
366 | throw new IllegalArgumentException("Unsupported URI:" + uri);
367 | }
368 | } catch (Exception e) {
369 | e.printStackTrace();
370 | } finally {
371 | dbConnection.endTransaction();
372 | }
373 |
374 | return null;
375 | }
376 |
377 | @Override
378 | public final int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
379 | final SQLiteDatabase dbConnection = mDatabase.getWritableDatabase();
380 | int updateCount = 0;
381 | List joinUris = new ArrayList();
382 |
383 | try {
384 | dbConnection.beginTransaction();
385 |
386 | switch (URI_MATCHER.match(uri)) {
387 | case YOUTUBEVIDEO_DIR :
388 | updateCount = dbConnection.update(YoutubeVideoTable.TABLE_NAME, values, selection, selectionArgs);
389 |
390 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
391 |
392 | dbConnection.setTransactionSuccessful();
393 | break;
394 | case YOUTUBEVIDEO_ID :
395 | final long youtubevideoId = ContentUris.parseId(uri);
396 | updateCount = dbConnection.update(YoutubeVideoTable.TABLE_NAME, values,
397 | YoutubeVideoTable._ID + "=" + youtubevideoId + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"), selectionArgs);
398 |
399 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
400 |
401 | dbConnection.setTransactionSuccessful();
402 | break;
403 |
404 | case YOUTUBECOMMENT_DIR :
405 | updateCount = dbConnection.update(YoutubeCommentTable.TABLE_NAME, values, selection, selectionArgs);
406 |
407 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
408 |
409 | dbConnection.setTransactionSuccessful();
410 | break;
411 | case YOUTUBECOMMENT_ID :
412 | final long youtubecommentId = ContentUris.parseId(uri);
413 | updateCount = dbConnection.update(YoutubeCommentTable.TABLE_NAME, values,
414 | YoutubeCommentTable._ID + "=" + youtubecommentId + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"), selectionArgs);
415 |
416 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
417 |
418 | dbConnection.setTransactionSuccessful();
419 | break;
420 |
421 | case ARTIST_DIR :
422 | updateCount = dbConnection.update(ArtistTable.TABLE_NAME, values, selection, selectionArgs);
423 |
424 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
425 |
426 | dbConnection.setTransactionSuccessful();
427 | break;
428 | case ARTIST_ID :
429 | final long artistId = ContentUris.parseId(uri);
430 | updateCount = dbConnection.update(ArtistTable.TABLE_NAME, values,
431 | ArtistTable._ID + "=" + artistId + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"), selectionArgs);
432 |
433 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
434 |
435 | dbConnection.setTransactionSuccessful();
436 | break;
437 |
438 | case SONG_DIR :
439 | updateCount = dbConnection.update(SongTable.TABLE_NAME, values, selection, selectionArgs);
440 |
441 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
442 |
443 | dbConnection.setTransactionSuccessful();
444 | break;
445 | case SONG_ID :
446 | final long songId = ContentUris.parseId(uri);
447 | updateCount = dbConnection.update(SongTable.TABLE_NAME, values,
448 | SongTable._ID + "=" + songId + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"), selectionArgs);
449 |
450 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
451 |
452 | dbConnection.setTransactionSuccessful();
453 | break;
454 |
455 | case RECORDEDVIDEO_DIR :
456 | updateCount = dbConnection.update(RecordedVideoTable.TABLE_NAME, values, selection, selectionArgs);
457 |
458 | dbConnection.setTransactionSuccessful();
459 | break;
460 | case RECORDEDVIDEO_ID :
461 | final long recordedvideoId = ContentUris.parseId(uri);
462 | updateCount = dbConnection.update(RecordedVideoTable.TABLE_NAME, values,
463 | RecordedVideoTable._ID + "=" + recordedvideoId + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"), selectionArgs);
464 |
465 | dbConnection.setTransactionSuccessful();
466 | break;
467 |
468 | default :
469 | throw new IllegalArgumentException("Unsupported URI:" + uri);
470 | }
471 | } finally {
472 | dbConnection.endTransaction();
473 | }
474 |
475 | if (updateCount > 0) {
476 | getContext().getContentResolver().notifyChange(uri, null);
477 |
478 | for (Uri joinUri : joinUris) {
479 | getContext().getContentResolver().notifyChange(joinUri, null);
480 | }
481 | }
482 |
483 | return updateCount;
484 |
485 | }
486 |
487 | @Override
488 | public final int delete(final Uri uri, final String selection, final String[] selectionArgs) {
489 | final SQLiteDatabase dbConnection = mDatabase.getWritableDatabase();
490 | int deleteCount = 0;
491 | List joinUris = new ArrayList();
492 |
493 | try {
494 | dbConnection.beginTransaction();
495 |
496 | switch (URI_MATCHER.match(uri)) {
497 | case YOUTUBEVIDEO_DIR :
498 | deleteCount = dbConnection.delete(YoutubeVideoTable.TABLE_NAME, selection, selectionArgs);
499 |
500 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
501 |
502 | dbConnection.setTransactionSuccessful();
503 | break;
504 | case YOUTUBEVIDEO_ID :
505 | deleteCount = dbConnection.delete(YoutubeVideoTable.TABLE_NAME, YoutubeVideoTable.WHERE_ID_EQUALS, new String[] { uri.getLastPathSegment() });
506 |
507 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
508 |
509 | dbConnection.setTransactionSuccessful();
510 | break;
511 |
512 | case YOUTUBECOMMENT_DIR :
513 | deleteCount = dbConnection.delete(YoutubeCommentTable.TABLE_NAME, selection, selectionArgs);
514 |
515 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
516 |
517 | dbConnection.setTransactionSuccessful();
518 | break;
519 | case YOUTUBECOMMENT_ID :
520 | deleteCount = dbConnection.delete(YoutubeCommentTable.TABLE_NAME, YoutubeCommentTable.WHERE_ID_EQUALS, new String[] { uri.getLastPathSegment() });
521 |
522 | joinUris.add(YOUTUBEVIDEO_JOIN_YOUTUBECOMMENT_CONTENT_URI);
523 |
524 | dbConnection.setTransactionSuccessful();
525 | break;
526 |
527 | case ARTIST_DIR :
528 | deleteCount = dbConnection.delete(ArtistTable.TABLE_NAME, selection, selectionArgs);
529 |
530 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
531 |
532 | dbConnection.setTransactionSuccessful();
533 | break;
534 | case ARTIST_ID :
535 | deleteCount = dbConnection.delete(ArtistTable.TABLE_NAME, ArtistTable.WHERE_ID_EQUALS, new String[] { uri.getLastPathSegment() });
536 |
537 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
538 |
539 | dbConnection.setTransactionSuccessful();
540 | break;
541 |
542 | case SONG_DIR :
543 | deleteCount = dbConnection.delete(SongTable.TABLE_NAME, selection, selectionArgs);
544 |
545 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
546 |
547 | dbConnection.setTransactionSuccessful();
548 | break;
549 | case SONG_ID :
550 | deleteCount = dbConnection.delete(SongTable.TABLE_NAME, SongTable.WHERE_ID_EQUALS, new String[] { uri.getLastPathSegment() });
551 |
552 | joinUris.add(ARTIST_JOIN_SONG_CONTENT_URI);
553 |
554 | dbConnection.setTransactionSuccessful();
555 | break;
556 |
557 | case RECORDEDVIDEO_DIR :
558 | deleteCount = dbConnection.delete(RecordedVideoTable.TABLE_NAME, selection, selectionArgs);
559 |
560 | dbConnection.setTransactionSuccessful();
561 | break;
562 | case RECORDEDVIDEO_ID :
563 | deleteCount = dbConnection.delete(RecordedVideoTable.TABLE_NAME, RecordedVideoTable.WHERE_ID_EQUALS, new String[] { uri.getLastPathSegment() });
564 |
565 | dbConnection.setTransactionSuccessful();
566 | break;
567 |
568 | default :
569 | throw new IllegalArgumentException("Unsupported URI:" + uri);
570 | }
571 | } finally {
572 | dbConnection.endTransaction();
573 | }
574 |
575 | if (deleteCount > 0) {
576 | getContext().getContentResolver().notifyChange(uri, null);
577 |
578 | for (Uri joinUri : joinUris) {
579 | getContext().getContentResolver().notifyChange(joinUri, null);
580 | }
581 | }
582 |
583 | return deleteCount;
584 | }
585 | }
--------------------------------------------------------------------------------
/ClientTest/src/com/rain/utils/android/robocop/clienttest/Main.java:
--------------------------------------------------------------------------------
1 | package com.rain.utils.android.robocop.clienttest;
2 |
3 | import com.rain.utils.android.robocop.generator.ContentProviderWriter;
4 |
5 | /**
6 | * Created with IntelliJ IDEA.
7 | * User: dustin
8 | * Date: 1/15/14
9 | * Time: 9:43 AM
10 | */
11 | public class Main {
12 | public static void main(String[] args) {
13 | // ContentProviderTableModel tableA = new ContentProviderTableModel("First");
14 | // tableA.addField("integerField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.Number);
15 | // tableA.addField("booleanField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.Bool);
16 | // tableA.addField("numericField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.Numeric);
17 | // tableA.addField("textField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.String);
18 | //
19 | //
20 | // ContentProviderTableModel tableB = new ContentProviderTableModel("Second");
21 | // tableB.addField("integerField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.Number);
22 | // tableB.addField("booleanField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.Bool);
23 | // tableB.addField("numericField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.Numeric);
24 | // tableB.addField("textField", ContentProviderTableModel.ContentProviderTableFieldModel.FieldType.String);
25 | //
26 | // List tables = new ArrayList();
27 | // tables.add(tableA);
28 | // tables.add(tableB);
29 | // ContentProviderModel model = new ContentProviderModel("com.rain.app", "Rain", 1, tables);
30 | //
31 | ContentProviderWriter writer = new ContentProviderWriter();
32 | writer.createContentProvider("ClientTest/resources/contentProviderSchema_simple.json", "ClientTest/src-gen/");
33 | // writer.createContentProvider(model,"ClientTest/src-gen/");
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | RoboCoP
2 | =======
3 |
4 | RoboCoP is a Java library that can generate a fully-functional ContentProvider from a simple JSON schema file.
5 |
6 | Get the latest version from our release page: https://github.com/mediarain/RoboCoP/releases
7 |
8 | Setup
9 | ========
10 | There are only a few steps involved in order to set up this tool to work inside of your build environment. The steps should not be difficult but should be done with care. At a high level you must:
11 | 1. Download and install the library jar file into your project directory
12 | 2. Include your own custom JSON schema file in your project directory
13 | 3. Configure your build.gradle file and include a special build task to generate the necessary files
14 | 4. Use the Gradle task to generate your ContentProvider and related files
15 | 5. Install your new ContentProvider in your AndroidManifest.xml file
16 | 6. Enjoy
17 |
18 | Download And Install
19 | --------
20 | ### Download and Install
21 | 1. Download the latest jar from the releases section of this repo page. You can use either the jar with dependencies (recommended) or provide them yourself.
22 | 2. Create a new folder in your project directory and name it whatever you want (eg 'RoboCoP'). In a typical Android Studio project, this will be '//RoboCoP/'
23 | 3. Place the RoboCoP jar from step 1 into the RoboCoP directory.
24 | a. It's important that you don't put this jar in your libs folder since this is a buildscript dependency not a runtime dependency and it will cause problems if this is confused.
25 | b. Don't add the path to this jar to your regular android dependencies in any other way as it will break your build. It's not a runtime dependency; it's just used by the build script.
26 |
27 | Create Your JSON Schema Definition
28 | ----------
29 | Create a JSON schema definition and place it in the same directory as the RoboCoP jar. You can name this file whatever you like, such as 'schema.json'. A sample schema file can be found in our Wiki: https://github.com/mediarain/RoboCoP/wiki/Example-JSON-schema-definition
30 |
31 | ### Schema File Structure
32 |
33 | ```json
34 | {
35 | "packageName" : "",
36 | "providerName" : "",
37 | "databaseVersion" : ,
38 | "tables" : [], //see below for table definition rules
39 | "relationships" : [] //see below for table definition rules
40 | }
41 | ```
42 |
43 | #### Table Definition Structure
44 |
45 | ```json
46 | {
47 | "name" : "",
48 | "members" : [] //see below for member field definition
49 | }
50 | ```
51 |
52 | ##### Table Field Definition Structure
53 |
54 | ```json
55 | {
56 | "type" : "",
57 | "name" : ""
58 | }
59 | ```
60 |
61 | #### Relationship Definition Structure
62 |
63 | ```json
64 | {
65 | "name" : "",
66 | "left_table" : "",
67 | "right_table" : "",
68 | "type" : "
69 | }
70 | ```
71 |
72 | Remember that for the code generation to generate nice looking code, you need to write all of your schema values in lower case and underscore-separated.
73 |
74 | Gradle Configuration
75 | ---------------
76 | It's important to note that the only build.gradle file you should be editing is the one lower down in your application module, not the top level build.gradle file.
77 |
78 | The Gradle configuration involves just a few steps:
79 | 1. Add a buildscript definition that depends on the RoboCoP library. You need to add the buildscript to your application module build.gradle file not the root level build.gradle file which already should have a build.
80 | 2. Add an import for the RoboCoP generator class
81 | 3. Add a build task for the code generation process
82 |
83 | A sample build.gradle file can be found in our Wiki: https://github.com/mediarain/RoboCoP/wiki/Example-build.gradle-file
84 |
85 | ### buildscript definition
86 |
87 | ```groovy
88 | // you may be temped to edit your top level build.gradle file because it sorta looks like this. Don't do it. Simply add this code block, to your application module build.gradle file and make sure the path to the jar file is correct.
89 | buildscript {
90 | repositories {
91 | mavenCentral()
92 | }
93 | dependencies {
94 | classpath 'com.android.tools.build:gradle:0.9.+'
95 |
96 | //this is the important part, the buildscript needs our library to generate the code.
97 | classpath files('RoboCoP/RoboCoP-0.5-jar-with-dependencies.jar')
98 | }
99 | }
100 | ```
101 |
102 |
103 | ### RoboCoP generator import
104 | Place the import statement near the top of the file such as right underneath the android plugin
105 |
106 | ```groovy
107 | apply plugin: 'android'
108 | import com.rain.utils.android.robocop.generator.*;
109 | ```
110 |
111 | ### Build Task
112 | Place the build task at the root level in your build.gradle file (not inside something like the android{} definition for example)
113 |
114 | ```groovy
115 | task contentProviderGen {
116 | description = 'Generating a beautiful ContentProvider and required classes'
117 | doLast {
118 | System.out.println("Generating ContentProvider...")
119 | String schemaFilename = 'RoboCoP/agenda_schema.json';//replace with the path to your schema
120 | String baseOutputDir = 'src/main/java/';
121 | //if gradle throws an error on the following line, you probably either don't have your import statement set or you have the wrong path in your buildscript definition
122 | ContentProviderGenerator.generateContentProvider(schemaFilename, baseOutputDir);
123 | }
124 | }
125 | ```
126 |
127 | #### Explanation of Build Task
128 | The above task definition should be pretty easy to follow. This simply allows gradle to execute our code generation task whenever you want. inside the doLast{} block is where the main customization takes place. the schemaFilename variable should point to the location of your JSON schema file (explained previously). the baseOutputDir specifies at what root directory your generated code should be placed. What your resulting package structure (and thus file structure) will depend on what you put in your schema definition for the 'packageName' variable. So if you put 'com.mycompany.awesomeandroidapp' as your packageName, then the resulting files will be under 'src/main/java/com/mycompany/awesomeandroidapp'.
129 | #### Alternative Output Directories
130 | The generator tool can write code to any directory for which it has permission. We will often place generated code in its own source folder outside our main src directory. Some prefer to keep all their sources, generated or otherwise, in the same location. So as an example, if you want to place your ContentProvider in another directory like 'src-gen' then your build task would look like this:
131 |
132 | ```groovy
133 | task contentProviderGen {
134 | description = 'Generating a beautiful ContentProvider and required classes'
135 | doLast {
136 | System.out.println("Generating ContentProvider...")
137 | String schemaFilename = 'RoboCoP/agenda_schema.json';
138 | String baseOutputDir = 'src-gen/';
139 | ContentProviderGenerator.generateContentProvider(schemaFilename, baseOutputDir);
140 | }
141 | }
142 | ```
143 |
144 | However, you are also responsible to make sure that 'src-gen' is included as a source folder in your build.gradle file. This can be done as follows:
145 |
146 | ```groovy
147 | sourceSets {
148 | main {
149 | java.srcDir 'src-gen'
150 | }
151 | }
152 | ```
153 |
154 | We personally prefer this latter method as it keeps our main source directory uncluttered and focused on our own application code. It works either way though.
155 |
156 | Running The Generator
157 | -----------
158 | Once all the above setup are complete the last step to getting things up and running is to simply run the custom Gradle build task we created. The easiest way to do this is from Android Studio by right clicking the 'contentProviderGen' task text and selecting the "Run 'gradle:contentProvid...'". This will add a new build configuration in the top menu build configuration drop down. It will also provide you with the option of saving this configuration permanently so that it can be re-run whenever you like. This is a convenient option to select as you will more than likely want to make changes throughout the course of your development process.
159 |
160 | ### Caution
161 | Whenever you run this task, all of your generated code will be replaced with the newly generated code. Also, any other classes that are in the same folders as your generated code will be removed. You should never place your hand written code in the same directories as our generated code.
162 |
163 | Installing The Provider - Important!!!
164 | ---------
165 | In order to use a ContentProvider in your Android app you must install it into your application's AndroidManifest file. The generator creates a special file in the root of your generated code called 'content-provider.xml' to assist with this step. Copy the contents of this file into your AndroidManifest file inside the node. The generated code has the provider's exported property set to false. If this is true, then other applications may be able to access your data. Only set this to true if you know what you are doing.
166 |
167 | Your Done, Enjoy! - Example Usage of a Generated ContentProvider
168 | ----------
169 | To learn more about the ContentProvider class and its usages, please read the official docs: http://developer.android.com/reference/android/content/ContentProvider.html and http://developer.android.com/guide/topics/providers/content-providers.html. The following are some examples that encompass most of our typical usages.
170 |
171 | ### Using a CursorLoader To Populate a ListView
172 | Using Loaders is a great way to simplify fetching data for which to populate your UI and take away many of the concerns that come with updating data when necessary and managing the lifecycle of your app.
173 | #### Implement LoaderCallbacks in your Activity or Fragment
174 |
175 | ```java
176 | public class AgendaListFragment extends ListFragment implements LoaderManager.LoaderCallbacks {
177 |
178 | @Override
179 | public void onCreate(Bundle savedInstanceState) {
180 |
181 | }
182 |
183 | @Override
184 | public Loader onCreateLoader(int id, Bundle args) {
185 |
186 | }
187 |
188 | @Override
189 | public void onLoadFinished(Loader loader, Cursor data) {
190 |
191 | }
192 |
193 | @Override
194 | public void onLoaderReset(Loader loader) {
195 |
196 | }
197 |
198 | }
199 | ```
200 |
201 | The LoaderCallbacks interface provides hooks for creating your Loader and responding to load events. You'll now have to create your Loader which will be a CursorLoader and handling the events.
202 | ##### Create a CursorLoader
203 |
204 | ```java
205 | @Override
206 | public Loader onCreateLoader(int id, Bundle args) {
207 | return new CursorLoader(getActivity(), AgendaProvider.AGENDA_CONTENT_URI,new String[]{AgendaTable._ID, AgendaTable.NAME},null,null,AgendaTable._ID + " DESC");
208 | }
209 | ```
210 |
211 | The generated ContentProvider has content URI's for all your tables and code completion should help you find the one you want. After that you can specify which fields you want to be returned and any query and query params or ordering need to go into the query.
212 |
213 | ##### Handle the load event callbacks
214 |
215 | ```java
216 | @Override
217 | public void onLoadFinished(Loader loader, Cursor data) {
218 | mAdapter.swapCursor(data);
219 | }
220 |
221 | @Override
222 | public void onLoaderReset(Loader loader) {
223 | mAdapter.swapCursor(null);
224 | }
225 | ```
226 |
227 | This is about the extent of it. when onLoadFinished will get called whenever a query to your table takes place which could be because you started/restarted the loader explicitly or because your backing data store changed which happens automatically with our generated code. onLoaderReset gets called during (suprise!) resets and so the current data is unavailable and so we pass in null to the cursor for the meantime.
228 |
229 | ##### Starting the loader
230 |
231 | ```java
232 | @Override
233 | public void onCreate(Bundle savedInstanceState) {
234 | super.onCreate(savedInstanceState);
235 |
236 | mAdapter = new AgendaAdapter(getActivity(), null, 0);
237 | setListAdapter(mAdapter);
238 | getLoaderManager().initLoader(0, null, this);
239 | }
240 | ```
241 |
242 | Once it's started, the CursorLoader will query your ContentProvider via the ContentResolver and return your data when it's ready (usually very quickly). Here is what a working ListFragment might look like.
243 |
244 | ```java
245 | public class AgendaListFragment extends ListFragment implements LoaderManager.LoaderCallbacks {
246 |
247 | @Override
248 | public void onCreate(Bundle savedInstanceState) {
249 | super.onCreate(savedInstanceState);
250 |
251 | mAdapter = new AgendaAdapter(getActivity(), null, 0);
252 | setListAdapter(mAdapter);
253 | getLoaderManager().initLoader(0, null, this);
254 | }
255 |
256 | @Override
257 | public Loader onCreateLoader(int id, Bundle args) {
258 | return new CursorLoader(getActivity(), AgendaProvider.AGENDA_CONTENT_URI,new String[]{AgendaTable._ID, AgendaTable.NAME},null,null,AgendaTable._ID + " DESC");
259 | }
260 |
261 | @Override
262 | public void onLoadFinished(Loader loader, Cursor data) {
263 | mAdapter.swapCursor(data);
264 | }
265 |
266 | @Override
267 | public void onLoaderReset(Loader loader) {
268 | mAdapter.swapCursor(null);
269 | }
270 |
271 | }
272 | ```
273 |
274 | ### Adding a new record to a table
275 | Building off the previous example, we'll demonstrate how to insert new records that will automatically get loaded into the ListFragment once inserted.
276 |
277 | ```java
278 | Agenda agenda = new Agenda();
279 | agenda.setName("New Agenda");
280 | getContentResolver().insert(AgendaProvider.AGENDA_CONTENT_URI, agenda.getContentValues());
281 | ```
282 |
283 | The generated tool includes convenient model classes that map to your table records. They can be constructed/inflated from a cursor and can also export a ContentValues object which is necessary for ContentResolver operations. Because the ListFragment CursorLoader is observing our Agenda table content URI, it, the onLoadFinished will get triggered automatically once this insert operation finishes and our new record will show up in our ListView. Sweet!
284 | ### Updating an existing record
285 | Let's now suppose that when the user taps on an agenda item from the list they are presented with a form with which to modify the details of the agenda. The following is an example of how to update the agenda item so that its values are saved and automatically reflected by our CursorLoader previously explained.
286 |
287 | ```java
288 | private void updateAgenda() {
289 | //mAgenda is an Agenda model object generated by this tool, it has all the getters and setters you need
290 | mAgenda.setName(mAgendaTitle.getText().toString());
291 | mAgenda.setPersonConducting(mPersonConducting.getText().toString());
292 | //there are also convenient constants on the generated Table classes for common necessary strings. AgendaTable.WHERE_ID_EQUALS resolves to "_id = ?"
293 | getContentResolver().update(AgendaProvider.AGENDA_CONTENT_URI,mAgenda.getContentValues(), AgendaTable.WHERE_ID_EQUALS,new String[]{agenda.getRowId().toString()});
294 | }
295 | ```
296 |
297 | ### Deleting a Record
298 | Sometimes we get tired of some records and we have to let them go. Here's how to do it.
299 |
300 | ```java
301 | getContentResolver().delete(AgendaProvider.AGENDA_CONTENT_URI, AgendaTable.WHERE_ID_EQUALS, agenda.getRowId().toString());
302 | ```
303 |
304 | ### Performing Batch Operations
305 | Sometimes you have to do a lot of operations and it is much more efficient to do them all as a batch rather than one at a time. Here's a contrived example:
306 |
307 | ```java
308 | public void updateLotsOfAgendasAtOnce(List agendas) {
309 | ArrayList operations = new ArrayList<>();
310 | for (int i = 0; i < agendas.size(); i++) {
311 | ContentProviderOperation operation = ContentProviderOperation.newUpdate(AgendaProvider.AGENDA_CONTENT_URI)
312 | .withSelection(AgendaTable.WHERE_ID_EQUALS, new String[]{agendas.get(i).getRowId().toString()}).build();
313 | operations.add(operation);
314 | }
315 | //just for fun, let's insert a few new ones while we're at it
316 | for (int i = 0; i < 10; i++) {
317 | Agenda agenda = new Agenda();
318 | agenda.setName("New Agenda " + i);
319 | ContentProviderOperation operation = ContentProviderOperation.newInsert(AgendaProvider.AGENDA_CONTENT_URI).withValues(agenda.getContentValues()).build();
320 | operations.add(operation);
321 |
322 | }
323 | try {
324 | getContentResolver().applyBatch(AgendaProvider.AUTHORITY, operations);
325 | } catch (RemoteException e) {
326 | e.printStackTrace();
327 | } catch (OperationApplicationException e) {
328 | e.printStackTrace();
329 | }
330 | }
331 | ```
332 |
333 | There's a couple really good blog posts about this pattern. Please chech them out:
334 | 1. The basics: https://www.grokkingandroid.com/better-performance-with-contentprovideroperation/
335 | 2. withBackReference explained (really useful): https://www.grokkingandroid.com/androids-contentprovideroperation-withbackreference-explained/
336 |
337 |
338 | FAQ's
339 | ----------
340 | ### Why should I use a ContentProvider? I thought those were just for sharing code between applications.
341 | Yes, the architecture and design decisions behind the ContentProvider is informed by the requirement that some data needs to be shared across application processes. However, we've found that the design also provides benefits for simply working with your own data and we've had great success using them. The biggest reason that we've found as to why more developers don't use them is simply because they are very labor intensive and really hard to get right when written by hand. This tool takes those drawbacks away. Here are a few of our top reasons for using a ContentProvider (not all of them are necessarily exclusive to ContentProviders).
342 | 1. If you do need to share code between Applications or Processes (several of the apps we've built do have multiple processes) then you're already setup.
343 | 2. Many of the Android components are designed to work with them out of the box. For example, CursorLoader takes a ContentProvider content URI and query to fetch results and automatically notify your UI of changes to backing data.
344 | 3. ContentResolver and ContentProvider are built into the system and relied upon heavily throughout Android (Contacts and Calendar are good examples) and so we benefit from a very robust and hardened system. Our generated code is quite small compared to other data abstraction mechanisms and ORM's and we feel comfort (and have had great success) relying heavily on system components rather than a custom system.
345 | 4. We like the client-service-oriented nature of the ContentResolver/ContentProvider architecture. Tables and data are accessed with URI's in much the same way you probably access yourserver-side data. We've observed improvements in the overall application architecture in our apps by communicating between components this way.
346 |
347 | ### What are the current limitations of your tool?
348 | Currently we only generate code that supports querying one-to-many joined table relationships. Our generated code does not allow you to insert/update joined records as a single insert/update. For example if you queried for the join between person and phone numbers you cannot send a request to insert/update to this joined relationship. You must send your insert/update requests directly to the tables they belong to. The generated code also does not currently support many-to-many join operations. These features are on the roadmap as we feel they are important for a complete solution. However, we've built this tool according to what has been needful on our applications and the nature of mobile applications predisposes our UI's to generally only consume data coming from single tables: lists of friends, lists of documents, list of tweets. Very few data presentations problems are solved more optimally than two queries: query for and show a list of people and then query for that person's contact details when the user taps on their record.
349 |
350 | License
351 | -------
352 |
353 | Copyright 2014 Crossborders, LLC. Rain (DBA)
354 |
355 | Licensed under the Apache License, Version 2.0 (the "License");
356 | you may not use this file except in compliance with the License.
357 | You may obtain a copy of the License at
358 |
359 | http://www.apache.org/licenses/LICENSE-2.0
360 |
361 | Unless required by applicable law or agreed to in writing, software
362 | distributed under the License is distributed on an "AS IS" BASIS,
363 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
364 | See the License for the specific language governing permissions and
365 | limitations under the License.
366 |
367 |
368 |
369 |
--------------------------------------------------------------------------------
/RoboCoP.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/libs/gson-2.2.4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mediarain/RoboCoP/789c52c9e9f68b4fbb40086bc360741af5117a8d/libs/gson-2.2.4.jar
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.rain.util.android
8 | RoboCoP
9 | 0.5.1
10 | jar
11 |
12 |
13 |
14 | org.apache.velocity
15 | velocity
16 | 1.7
17 |
18 |
19 |
20 | com.google.code.gson
21 | gson
22 | 2.2.4
23 |
24 |
25 |
26 |
27 | 1.6
28 |
29 |
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-compiler-plugin
35 |
36 | -proc:none
37 |
38 |
39 |
40 | maven-assembly-plugin
41 |
42 |
43 | package
44 |
45 | single
46 |
47 |
48 |
49 |
50 |
51 | jar-with-dependencies
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/main/java/com/rain/utils/android/robocop/generator/ContentProviderGenerator.java:
--------------------------------------------------------------------------------
1 | package com.rain.utils.android.robocop.generator;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: dustin
6 | * Date: 1/22/14
7 | * Time: 11:21 AM
8 | */
9 | public class ContentProviderGenerator {
10 |
11 | public static void generateContentProvider(String schemaPath, String generatedSourcePath) {
12 | ContentProviderWriter writer = new ContentProviderWriter();
13 | writer.createContentProvider(schemaPath, generatedSourcePath);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/rain/utils/android/robocop/generator/ContentProviderWriter.java:
--------------------------------------------------------------------------------
1 | package com.rain.utils.android.robocop.generator;
2 |
3 | import com.google.gson.Gson;
4 | import org.apache.velocity.Template;
5 | import org.apache.velocity.VelocityContext;
6 | import org.apache.velocity.app.VelocityEngine;
7 | import com.rain.utils.android.robocop.model.*;
8 |
9 | import java.io.*;
10 | import java.net.URL;
11 | import java.util.Properties;
12 |
13 | /**
14 | * Created with IntelliJ IDEA.
15 | * User: dustin
16 | * Date: 1/15/14
17 | * Time: 9:44 AM
18 | */
19 | public class ContentProviderWriter {
20 |
21 | public void createContentProvider(String schemaPath, String sourcePath) {
22 | Gson gson = new Gson();
23 | try {
24 | ContentProviderModel model = gson.fromJson(readFile(schemaPath), ContentProviderModel.class);
25 | model.inflateRelationships();
26 | createContentProvider(model, sourcePath);
27 | } catch (IOException e) {
28 | e.printStackTrace();
29 | }
30 | }
31 |
32 | public void createContentProvider(ContentProviderModel contentProviderModel, String sourcePath) {
33 | //clear out any generated source folders
34 | //the provider path
35 | final String providerPath = getFilePath(sourcePath, contentProviderModel.getPackage(), "provider");
36 | removeDirectoryContents(providerPath);
37 | final String providerXMLPath = getFilePath(sourcePath, contentProviderModel.getPackage(), null);
38 | removeDirectoryContents(providerXMLPath);
39 | final String databasePath = getFilePath(sourcePath, contentProviderModel.getPackage(), "database");
40 | removeDirectoryContents(databasePath);
41 | final String tablePath = getFilePath(sourcePath, contentProviderModel.getPackage(), "database/table");
42 | removeDirectoryContents(tablePath);
43 | final String modelPath = getFilePath(sourcePath, contentProviderModel.getPackage(), "model");
44 | removeDirectoryContents(modelPath);
45 |
46 | Properties props = new Properties();
47 | URL url = this.getClass().getClassLoader().getResource("velocity.properties");
48 | try {
49 | props.load(url.openStream());
50 | VelocityEngine engine = new VelocityEngine(props);
51 | engine.init();
52 |
53 | VelocityContext baseContext = new VelocityContext();
54 | baseContext.put("packageName", contentProviderModel.getPackage());
55 | baseContext.put("providerModel", contentProviderModel);
56 |
57 | VelocityContext providerContext = new VelocityContext(baseContext);
58 | providerContext.put("providerName", contentProviderModel.getProviderName());
59 | providerContext.put("tables", contentProviderModel.getTables());
60 | providerContext.put("relationships", contentProviderModel.getRelationships());
61 | writeFile(engine, providerContext, "ContentProvider.vm", providerPath, "/" + contentProviderModel.getProviderName() + "Provider.java");
62 | writeFile(engine, providerContext, "ProviderXML.vm", providerXMLPath, "/content-provider.xml");
63 |
64 | VelocityContext databaseContext = new VelocityContext(providerContext);
65 | databaseContext.put("databaseVersion", contentProviderModel.getDatabaseVersion());
66 | writeFile(engine, databaseContext, "Database.vm", databasePath, "/" + contentProviderModel.getProviderName() + "Database.java");
67 |
68 | for (ContentProviderTableModel table : contentProviderModel.getTables()) {
69 | VelocityContext tableContext = new VelocityContext(baseContext);
70 | tableContext.put("table", table);
71 | tableContext.put("participatingRelationships", contentProviderModel.getRelationshipsForTable(table));
72 | tableContext.put("tableName", table.getTableClassName());
73 | tableContext.put("fields", table.getFields());
74 | writeFile(engine, tableContext, "Table.vm", tablePath, "/" + table.getTableClassName() + "Table.java");
75 | writeFile(engine, tableContext, "Model.vm", modelPath, "/" + table.getTableClassName() + ".java");
76 | }
77 |
78 | } catch (IOException e) {
79 |
80 | }
81 | }
82 |
83 | String readFile(String fileName) throws IOException {
84 | BufferedReader br = new BufferedReader(new FileReader(fileName));
85 | try {
86 | StringBuilder sb = new StringBuilder();
87 | String line = br.readLine();
88 |
89 | while (line != null) {
90 | sb.append(line);
91 | sb.append("\n");
92 | line = br.readLine();
93 | }
94 | return sb.toString();
95 | } finally {
96 | br.close();
97 | }
98 | }
99 |
100 | private void removeDirectoryContents(String rootFolderPath) {
101 | File directory = new File(rootFolderPath);
102 | removeFilesAndFoldersBelow(directory);
103 | }
104 |
105 | private void removeFilesAndFoldersBelow(File directory) {
106 | if(directory.exists()){
107 | File[] files = directory.listFiles();
108 | if(null!=files){
109 | for(int i=0; i mTables;
27 |
28 | @SerializedName("relationships")
29 | private List mRelationships;
30 |
31 | public ContentProviderModel(String packageName, String providerName, int databaseVersion, List tables, List relationships) {
32 | mPackage = packageName;
33 | mProviderName = providerName;
34 | mDatabaseVersion = databaseVersion;
35 | mTables = tables;
36 | mRelationships = relationships;
37 | }
38 |
39 | public String getProviderName() {
40 |
41 | return StringUtils.convertToTitleCase(mProviderName);
42 | }
43 |
44 | public List getTables() {
45 | return mTables;
46 | }
47 |
48 | public String getPackage() {
49 | return mPackage;
50 | }
51 |
52 | public int getDatabaseVersion() {
53 | return mDatabaseVersion;
54 | }
55 |
56 | public List getRelationships() {
57 | return mRelationships;
58 | }
59 |
60 | public List getRelationshipsForTable(ContentProviderTableModel tableModel) {
61 | if (tableModel == null || mRelationships == null) return null;
62 | List includedRelationships = new ArrayList();
63 | for (ContentProviderRelationshipModel relationship : mRelationships) {
64 | if (relationship.getLeftTableModel() == tableModel || relationship.getRightTableModel() == tableModel) {
65 | includedRelationships.add(relationship);
66 | }
67 | }
68 | return includedRelationships;
69 | }
70 |
71 | public void inflateRelationships() {
72 | if (mRelationships != null) {
73 | for (ContentProviderRelationshipModel relationship : mRelationships) {
74 | String leftTableName = relationship.getLeftTableName();
75 | String rightTableName = relationship.getRightTableName();
76 | if (leftTableName == null || leftTableName.length() == 0 || rightTableName == null || rightTableName.length() == 0) {
77 | //invalid relationship config, bail
78 | System.out.println("invalid relationship config!!! one of the table names is missin or is blank");
79 | return;
80 | }
81 | ContentProviderTableModel leftTable = null;
82 | ContentProviderTableModel rightTable = null;
83 | for (ContentProviderTableModel table : mTables) {
84 | if (leftTable != null && rightTable != null) {
85 | break;
86 | }
87 | if (table.getTableName().equals(leftTableName)) {
88 | leftTable = table;
89 | }
90 | if (table.getTableName().equals(rightTableName)) {
91 | rightTable = table;
92 | }
93 | }
94 | if (leftTable == null || rightTable == null) {
95 | // the referenced tables could not be found
96 | System.out.println("one or both of the referenced tables in a relationship could not be found in the table definition. please check your spelling");
97 | return;
98 | }
99 | relationship.setLeftTableModel(leftTable);
100 | relationship.setRightTableModel(rightTable);
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/rain/utils/android/robocop/model/ContentProviderRelationshipModel.java:
--------------------------------------------------------------------------------
1 | package com.rain.utils.android.robocop.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * Created with IntelliJ IDEA.
7 | * User: dustin
8 | * Date: 2/4/14
9 | * Time: 4:52 PM
10 | */
11 | public class ContentProviderRelationshipModel {
12 |
13 | public static final String RELATIONSHIP_TYPE_TO_ONE = "to_one";
14 | public static final String RELATIONSHIP_TYPE_TO_MANY = "to_many";
15 | public static final String RELATIONSHIP_TYPE_MANY_TO_MANY = "many_to_many";
16 |
17 | @SerializedName("type")
18 | private String mReferenceType;
19 |
20 | @SerializedName("name")
21 | private String mCustomName;
22 |
23 | @SerializedName("left_table")
24 | private String mLeftTableName;
25 |
26 | private ContentProviderTableModel mLeftTableModel;
27 |
28 | @SerializedName("right_table")
29 | private String mRightTableName;
30 |
31 | private ContentProviderTableModel mRightTableModel;
32 |
33 | public ContentProviderRelationshipModel(String referenceType, String customName, String leftTableName, String rightTableName) {
34 | mReferenceType = referenceType;
35 | mCustomName = customName;
36 | mLeftTableName = leftTableName;
37 | mRightTableName = rightTableName;
38 | }
39 |
40 | public String getReferenceType() {
41 | return mReferenceType;
42 | }
43 |
44 | public ContentProviderTableModel getLeftTableModel() {
45 | return mLeftTableModel;
46 | }
47 |
48 | public void setLeftTableModel(ContentProviderTableModel leftTableModel) {
49 | mLeftTableModel = leftTableModel;
50 | }
51 |
52 | public ContentProviderTableModel getRightTableModel() {
53 | return mRightTableModel;
54 | }
55 |
56 | public void setRightTableModel(ContentProviderTableModel rightTableModel) {
57 | mRightTableModel = rightTableModel;
58 | }
59 |
60 | public String getLeftTableName() {
61 | return mLeftTableName;
62 | }
63 |
64 | public String getRightTableName() {
65 | return mRightTableName;
66 | }
67 |
68 | public String getLeftTableClassName() {
69 | return StringUtils.convertToTitleCase(mLeftTableName);
70 | }
71 |
72 | public String getRightTableClassName() {
73 | return StringUtils.convertToTitleCase(mRightTableName);
74 | }
75 |
76 | public String getLeftTableConstantName() {
77 | return StringUtils.getConstantString(mLeftTableName);
78 | }
79 |
80 | public String getRightTableConstantName() {
81 | return StringUtils.getConstantString(mRightTableName);
82 | }
83 |
84 | public String getLeftTableForeignKey() {
85 | return StringUtils.getConstantString(mLeftTableName) + "_ID";
86 | }
87 |
88 | public String getCustomName() {
89 | return mCustomName;
90 | }
91 |
92 | public void setCustomName(String customName) {
93 | mCustomName = customName;
94 | }
95 |
96 | public String getForeignKeyNameForTable(ContentProviderTableModel table) {
97 | if (table == null) return null;
98 | if (mReferenceType.equals(RELATIONSHIP_TYPE_TO_MANY) && mRightTableModel == table) {
99 | return mLeftTableModel.getTableConstantName() + "_ID";
100 | }
101 | return null;
102 | }
103 |
104 | public String getForeignKeyPrivateVariableNameForTable(ContentProviderTableModel table) {
105 | String constantName = getForeignKeyNameForTable(table);
106 | if (constantName == null) return null;
107 | constantName = StringUtils.convertToTitleCase(constantName);
108 | return "m"+constantName;
109 | }
110 |
111 | public String getForeignKeyVariableNameForTable(ContentProviderTableModel table) {
112 | String constantName = getForeignKeyNameForTable(table);
113 | if (constantName == null) return null;
114 | return StringUtils.convertToCamelCase(constantName);
115 | }
116 |
117 | public String getForeignKeyVariableAsTitleCase(ContentProviderTableModel table) {
118 | String constantName = getForeignKeyNameForTable(table);
119 | if (constantName == null) return null;
120 | return StringUtils.convertToTitleCase(constantName);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/main/java/com/rain/utils/android/robocop/model/ContentProviderTableModel.java:
--------------------------------------------------------------------------------
1 | package com.rain.utils.android.robocop.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * Created with IntelliJ IDEA.
10 | * User: dustin
11 | * Date: 1/15/14
12 | * Time: 9:46 AM
13 | */
14 | public class ContentProviderTableModel {
15 |
16 | @SerializedName("name")
17 | private String mTableName;
18 |
19 | @SerializedName("members")
20 | private List mFields = new ArrayList();
21 |
22 | public ContentProviderTableModel(String tableName) {
23 | mTableName = tableName;
24 | }
25 |
26 | public List getFields() {
27 | return mFields;
28 | }
29 |
30 | public void addField(String fieldName, String type) {
31 | mFields.add(new ContentProviderTableFieldModel(type, fieldName));
32 | }
33 |
34 | public String getTableName() {
35 | return mTableName;
36 | }
37 |
38 | public String getTableClassName() {
39 | return StringUtils.convertToTitleCase(mTableName);
40 | }
41 |
42 | public String getTableConstantName() {
43 | return StringUtils.getConstantString(mTableName);
44 | }
45 |
46 |
47 | public static class ContentProviderTableFieldModel {
48 |
49 | public static final String STRING = "string";
50 | public static final String DOUBLE = "double";
51 | public static final String INT = "int";
52 | public static final String BOOLEAN = "boolean";
53 | public static final String LONG = "long";
54 |
55 | @SerializedName("type")
56 | private String mFieldType;
57 |
58 | @SerializedName("name")
59 | private String mFieldName;
60 |
61 | public ContentProviderTableFieldModel(String fieldType, String fieldName) {
62 | mFieldType = fieldType;
63 | mFieldName = fieldName;
64 | }
65 |
66 | public String getFieldType() {
67 | return mFieldType;
68 | }
69 |
70 | public String getFieldName() {
71 | return mFieldName;
72 | }
73 |
74 | public String getConstantString() {
75 | return StringUtils.getConstantString(mFieldName);
76 | }
77 |
78 | public String getTypeString() {
79 | if (mFieldType.equals(INT) || mFieldType.equals(BOOLEAN)) {
80 | return "INTEGER";
81 | } else if (mFieldType.equals(LONG) || mFieldType.equals(DOUBLE)) {
82 | return "NUMERIC";
83 | } else {
84 | return "TEXT";
85 | }
86 | }
87 |
88 | public String getJavaTypeString() {
89 | String typeLower = mFieldType.toLowerCase();
90 | if (typeLower.equals(BOOLEAN)) {
91 | return "boolean";
92 | }
93 | if (typeLower.equals(INT)) {
94 | return "int";
95 | } else if (typeLower.equals(LONG) || typeLower.equals(DOUBLE)) {
96 | return "double";
97 | } else {
98 | return "String";
99 | }
100 | }
101 |
102 | public String getJavaTypeStringGetter() {
103 | if (mFieldType.equals(INT) || mFieldType.equals(BOOLEAN)) {
104 | return "getInt";
105 | } else if (mFieldType.equals(LONG)) {
106 | return "getLong";
107 | } else if(mFieldType.equals(DOUBLE)) {
108 | return "getDouble";
109 | } else {
110 | return "getString";
111 | }
112 | }
113 |
114 | public String getPrivateVariableName() {
115 | return StringUtils.getPrivateVariableName(mFieldName);
116 | }
117 |
118 | public String getNameAsTitleCase() {
119 | return StringUtils.convertToTitleCase(mFieldName);
120 | }
121 |
122 |
123 | }
124 |
125 | @Override
126 | public boolean equals(Object o) {
127 | return ((ContentProviderTableModel)o).getTableName().equals(getTableName());
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/java/com/rain/utils/android/robocop/model/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.rain.utils.android.robocop.model;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: dustin
6 | * Date: 1/16/14
7 | * Time: 5:07 PM
8 | */
9 | public class StringUtils {
10 |
11 | public static String getPrivateVariableName(String input) {
12 | input = convertToCamelCase(input);
13 | String firstChar = input.substring(0, 1);
14 | String remaining = input.substring(1, input.length());
15 | return "m" + firstChar.toUpperCase() + remaining;
16 | }
17 |
18 | public static String convertToCamelCase(String input) {
19 | String[] parts = input.split("_");
20 | String camelCaseString = "";
21 | for (int i = 0; i < parts.length; i++) {
22 | String part = parts[i];
23 | camelCaseString = camelCaseString + ((i == 0) ? part.toLowerCase() : toProperCase(part));
24 | // camelCaseString = camelCaseString + toProperCase(part);
25 | }
26 | return camelCaseString;
27 | }
28 |
29 | public static String convertToTitleCase(String input) {
30 | String[] parts = input.split("_");
31 | String camelCaseString = "";
32 | for (String part : parts){
33 | camelCaseString = camelCaseString + toProperCase(part);
34 | }
35 | return camelCaseString;
36 | }
37 |
38 | public static String getUnderscoreNameString(String input) {
39 | String regex = "([a-z])([A-Z])";
40 | String replacement = "$1_$2";
41 | return input.replaceAll(regex, replacement);
42 | }
43 |
44 | public static String getConstantString(String input) {
45 | String constant = StringUtils.getUnderscoreNameString(input).toUpperCase();
46 | return constant;
47 | }
48 |
49 | private static String toProperCase(String s) {
50 | return s.substring(0, 1).toUpperCase() +
51 | s.substring(1).toLowerCase();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/resources/ContentProvider.vm:
--------------------------------------------------------------------------------
1 | package ${packageName}.provider;
2 |
3 | import ${packageName}.database.${providerName}Database;
4 |
5 | import ${packageName}.database.table.*;
6 |
7 | import android.provider.BaseColumns;
8 | import android.text.TextUtils;
9 | import android.content.ContentUris;
10 | import android.database.sqlite.SQLiteQueryBuilder;
11 |
12 | import android.content.ContentProvider;
13 | import android.content.ContentValues;
14 | import android.content.UriMatcher;
15 | import android.database.Cursor;
16 | import android.database.sqlite.SQLiteDatabase;
17 | import android.net.Uri;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | public class ${providerName}Provider extends ContentProvider {
23 |
24 | public static final String AUTHORITY = "${packageName}.provider";
25 |
26 | public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
27 |
28 | #foreach( $table in $tables )
29 | #set( $tableName = $table.getTableConstantName() )
30 | public static final Uri ${tableName}_CONTENT_URI = Uri.withAppendedPath(${providerName}Provider.AUTHORITY_URI, ${table.getTableClassName()}Content.CONTENT_PATH);
31 |
32 | #end
33 | #foreach( $relationship in $relationships )
34 | #set( $joinTableName = $relationship.getLeftTableConstantName() + "_JOIN_" + $relationship.getRightTableConstantName())
35 | #set( $joinTableClassName = $relationship.getLeftTableClassName() + "Join" + $relationship.getRightTableClassName())
36 | public static final Uri ${joinTableName}_CONTENT_URI = Uri.withAppendedPath(${providerName}Provider.AUTHORITY_URI, ${joinTableClassName}Content.CONTENT_PATH);
37 | #end
38 |
39 | private static final UriMatcher URI_MATCHER;
40 | private ${providerName}Database mDatabase;
41 |
42 | #set( $matcherIndex = 0 )
43 | #foreach( $table in $tables )
44 | #set( $tableName = $table.getTableConstantName() )
45 | private static final int ${tableName}_DIR = ${matcherIndex};
46 | #set($matcherIndex = $matcherIndex+1)
47 | private static final int ${tableName}_ID = ${matcherIndex};
48 |
49 | #set($matcherIndex = $matcherIndex+1)
50 | #end
51 | #foreach( $relationship in $relationships )
52 | #set( $joinTableName = $relationship.getLeftTableConstantName() + "_JOIN_" + $relationship.getRightTableConstantName())
53 | private static final int ${joinTableName}_DIR = ${matcherIndex};
54 |
55 | #set($matcherIndex = $matcherIndex+1)
56 | #end
57 |
58 | static {
59 | URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
60 | #foreach( $table in $tables )
61 | #set( $tableNameCaps = $table.getTableConstantName() )
62 | URI_MATCHER.addURI(AUTHORITY, ${table.getTableClassName()}Content.CONTENT_PATH, ${tableNameCaps}_DIR);
63 | URI_MATCHER.addURI(AUTHORITY, ${table.getTableClassName()}Content.CONTENT_PATH + "/#", ${tableNameCaps}_ID);
64 |
65 | #end
66 | #foreach ( $relationship in $relationships )
67 | #set( $joinTableClassName = $relationship.getLeftTableClassName() + "Join" + $relationship.getRightTableClassName())
68 | #set( $joinTableName = $relationship.getLeftTableConstantName() + "_JOIN_" + $relationship.getRightTableConstantName())
69 | URI_MATCHER.addURI(AUTHORITY, ${joinTableClassName}Content.CONTENT_PATH, ${joinTableName}_DIR);
70 | #end
71 | }
72 |
73 | #foreach( $table in $tables )
74 | #set( $tableNameLower = $table.getTableClassName().toLowerCase() )
75 | #set( $providerNameLower = $providerName.toLowerCase() )
76 | public static final class ${table.getTableClassName()}Content implements BaseColumns {
77 | public static final String CONTENT_PATH = "${tableNameLower}";
78 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.${providerNameLower}_database.${tableNameLower}";
79 | public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.${providerNameLower}_database.${tableNameLower}";
80 | }
81 |
82 | #end
83 | #foreach( $relationship in $relationships )
84 | #set( $joinTableClassName = $relationship.getLeftTableClassName() + "Join" + $relationship.getRightTableClassName())
85 | #set( $joinTableName = $relationship.getLeftTableConstantName() + "_JOIN_" + $relationship.getRightTableConstantName())
86 | #set( $joinTableNameLower = $joinTableName.toLowerCase() )
87 | public static final class ${joinTableClassName}Content implements BaseColumns {
88 | public static final String CONTENT_PATH = "${joinTableNameLower}";
89 | public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.${providerNameLower}_database.${joinTableNameLower}";
90 | }
91 | #end
92 |
93 | @Override
94 | public final boolean onCreate() {
95 | mDatabase = new ${providerName}Database(getContext());
96 | return true;
97 | }
98 |
99 | @Override
100 | public final String getType(final Uri uri) {
101 | switch (URI_MATCHER.match(uri)) {
102 | #foreach( $table in $tables )
103 | #set( $tableNameCaps = $table.getTableConstantName() )
104 | case ${tableNameCaps}_DIR:
105 | return ${table.getTableClassName()}Content.CONTENT_TYPE;
106 | case ${tableNameCaps}_ID:
107 | return ${table.getTableClassName()}Content.CONTENT_ITEM_TYPE;
108 |
109 | #end
110 | #foreach( $relationship in $relationships)
111 | #set( $joinTableName = $relationship.getLeftTableConstantName() + "_JOIN_" + $relationship.getRightTableConstantName())
112 | #set( $joinTableClassName = $relationship.getLeftTableClassName() + "Join" + $relationship.getRightTableClassName())
113 | case ${joinTableName}_DIR:
114 | return ${joinTableClassName}Content.CONTENT_TYPE;
115 |
116 | #end
117 | default:
118 | throw new IllegalArgumentException("Unsupported URI: " + uri);
119 | }
120 | }
121 |
122 | @Override
123 | public final Cursor query(final Uri uri, String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) {
124 | final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
125 | final SQLiteDatabase dbConnection = mDatabase.getReadableDatabase();
126 |
127 | switch (URI_MATCHER.match(uri)) {
128 | #foreach( $table in $tables )
129 | #set( $tableNameCaps = $table.getTableConstantName() )
130 | case ${tableNameCaps}_ID:
131 | queryBuilder.appendWhere(${table.getTableClassName()}Table._ID + "=" + uri.getLastPathSegment());
132 | case ${tableNameCaps}_DIR:
133 | queryBuilder.setTables(${table.getTableClassName()}Table.TABLE_NAME);
134 | break;
135 |
136 | #end
137 | #foreach( $relationship in $relationships )
138 | #set( $joinTableName = $relationship.getLeftTableConstantName() + "_JOIN_" + $relationship.getRightTableConstantName())
139 | case ${joinTableName}_DIR:
140 | queryBuilder.setTables(${relationship.getLeftTableClassName()}Table.TABLE_NAME + " JOIN " + ${relationship.getRightTableClassName()}Table.TABLE_NAME + " ON (" + ${relationship.getLeftTableClassName()}Table.TABLE_NAME + "." + ${relationship.getLeftTableClassName()}Table._ID + "=" + ${relationship.getRightTableClassName()}Table.TABLE_NAME + "." + ${relationship.getRightTableClassName()}Table.${relationship.getLeftTableForeignKey()} + ")");
141 |
142 | projection = new String[] {
143 | //add left table columns
144 | ${relationship.getLeftTableClassName()}Table.TABLE_NAME + "._id AS " + ${relationship.getLeftTableClassName()}Table.TABLE_NAME + "__id",
145 | #foreach( $field in $relationship.getLeftTableModel().getFields())
146 | #if($field)
147 | #set( $fullFieldName = $relationship.getLeftTableClassName() + "Table." + $field.getConstantString() )
148 | #set( $fullTableName = $relationship.getLeftTableClassName() + "Table.TABLE_NAME" )
149 | ${fullTableName} + "." + ${fullFieldName} + " AS " + ${fullTableName} + "_" + ${fullFieldName},
150 | #end
151 | #end
152 | ${relationship.getRightTableClassName()}Table.TABLE_NAME + "._id AS " + ${relationship.getRightTableClassName()}Table.TABLE_NAME + "__id",
153 | #foreach( $field in $relationship.getRightTableModel().getFields())
154 | #if($field)
155 | #set( $fullFieldName = $relationship.getRightTableClassName() + "Table." + $field.getConstantString() )
156 | #set( $fullTableName = $relationship.getRightTableClassName() + "Table.TABLE_NAME" )
157 | ${fullTableName} + "." + ${fullFieldName} + " AS " + ${fullTableName} + "_" + ${fullFieldName},
158 | #end
159 | #end
160 | };
161 | break;
162 | #end
163 | default :
164 | throw new IllegalArgumentException("Unsupported URI:" + uri);
165 | }
166 |
167 | Cursor cursor = queryBuilder.query(dbConnection, projection, selection, selectionArgs, null, null, sortOrder);
168 | cursor.setNotificationUri(getContext().getContentResolver(), uri);
169 |
170 | return cursor;
171 |
172 | }
173 |
174 | @Override
175 | public final Uri insert(final Uri uri, final ContentValues values) {
176 | final SQLiteDatabase dbConnection = mDatabase.getWritableDatabase();
177 |
178 | try {
179 | dbConnection.beginTransaction();
180 |
181 | switch (URI_MATCHER.match(uri)) {
182 | #foreach( $table in $tables )
183 | #set( $tableNameCaps = $table.getTableConstantName() )
184 | #set( $tableNameLower = $tableNameCaps.toLowerCase() )
185 | case ${tableNameCaps}_DIR:
186 | case ${tableNameCaps}_ID:
187 | final long ${tableNameLower}Id = dbConnection.insertOrThrow(${table.getTableClassName()}Table.TABLE_NAME, null, values);
188 | final Uri new${table.getTableClassName()}Uri = ContentUris.withAppendedId(${tableNameCaps}_CONTENT_URI, ${tableNameLower}Id);
189 | getContext().getContentResolver().notifyChange(new${table.getTableClassName()}Uri, null);
190 | #set( $includedRelations = $providerModel.getRelationshipsForTable($table) )
191 | #if( $includedRelations )
192 | #foreach( $includedRelation in $includedRelations )
193 | #set( $joinTableName = $includedRelation.getLeftTableConstantName() + "_JOIN_" + $includedRelation.getRightTableConstantName() + "_CONTENT_URI")
194 | getContext().getContentResolver().notifyChange(${joinTableName}, null);
195 | #end
196 | #end
197 |
198 | dbConnection.setTransactionSuccessful();
199 | return new${table.getTableClassName()}Uri;
200 | #end
201 | default :
202 | throw new IllegalArgumentException("Unsupported URI:" + uri);
203 | }
204 | } catch (Exception e) {
205 | e.printStackTrace();
206 | } finally {
207 | dbConnection.endTransaction();
208 | }
209 |
210 | return null;
211 | }
212 |
213 | @Override
214 | public final int update(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
215 | final SQLiteDatabase dbConnection = mDatabase.getWritableDatabase();
216 | int updateCount = 0;
217 | List joinUris = new ArrayList();
218 |
219 | try {
220 | dbConnection.beginTransaction();
221 |
222 | switch (URI_MATCHER.match(uri)) {
223 | #foreach( $table in $tables )
224 | #set( $tableNameCaps = $table.getTableConstantName() )
225 | #set( $tableNameLower = $tableNameCaps.toLowerCase() )
226 | #set( $includedRelations = $providerModel.getRelationshipsForTable($table) )
227 | case ${tableNameCaps}_DIR:
228 | updateCount = dbConnection.update(${table.getTableClassName()}Table.TABLE_NAME, values, selection, selectionArgs);
229 |
230 | #if( $includedRelations )
231 | #foreach( $includedRelation in $includedRelations )
232 | #set( $joinTableName = $includedRelation.getLeftTableConstantName() + "_JOIN_" + $includedRelation.getRightTableConstantName() + "_CONTENT_URI")
233 | joinUris.add(${joinTableName});
234 |
235 | #end
236 | #end
237 | dbConnection.setTransactionSuccessful();
238 | break;
239 | case ${tableNameCaps}_ID:
240 | final long ${tableNameLower}Id = ContentUris.parseId(uri);
241 | updateCount = dbConnection.update(${table.getTableClassName()}Table.TABLE_NAME, values,
242 | ${table.getTableClassName()}Table._ID + "=" + ${tableNameLower}Id + (TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"), selectionArgs);
243 |
244 | #if( $includedRelations )
245 | #foreach( $includedRelation in $includedRelations )
246 | #set( $joinTableName = $includedRelation.getLeftTableConstantName() + "_JOIN_" + $includedRelation.getRightTableConstantName() + "_CONTENT_URI")
247 | joinUris.add(${joinTableName});
248 |
249 | #end
250 | #end
251 | dbConnection.setTransactionSuccessful();
252 | break;
253 |
254 | #end
255 | default :
256 | throw new IllegalArgumentException("Unsupported URI:" + uri);
257 | }
258 | } finally {
259 | dbConnection.endTransaction();
260 | }
261 |
262 | if (updateCount > 0) {
263 | getContext().getContentResolver().notifyChange(uri, null);
264 |
265 | for (Uri joinUri : joinUris) {
266 | getContext().getContentResolver().notifyChange(joinUri, null);
267 | }
268 | }
269 |
270 | return updateCount;
271 |
272 | }
273 |
274 | @Override
275 | public final int delete(final Uri uri, final String selection, final String[] selectionArgs) {
276 | final SQLiteDatabase dbConnection = mDatabase.getWritableDatabase();
277 | int deleteCount = 0;
278 | List joinUris = new ArrayList();
279 |
280 | try {
281 | dbConnection.beginTransaction();
282 |
283 | switch (URI_MATCHER.match(uri)) {
284 | #foreach( $table in $tables )
285 | #set( $tableNameCaps = $table.getTableConstantName() )
286 | #set( $tableNameLower = $tableNameCaps.toLowerCase() )
287 | #set( $includedRelations = $providerModel.getRelationshipsForTable($table) )
288 | case ${tableNameCaps}_DIR:
289 | deleteCount = dbConnection.delete(${table.getTableClassName()}Table.TABLE_NAME, selection, selectionArgs);
290 |
291 | #if( $includedRelations )
292 | #foreach( $includedRelation in $includedRelations )
293 | #set( $joinTableName = $includedRelation.getLeftTableConstantName() + "_JOIN_" + $includedRelation.getRightTableConstantName() + "_CONTENT_URI")
294 | joinUris.add(${joinTableName});
295 |
296 | #end
297 | #end
298 | dbConnection.setTransactionSuccessful();
299 | break;
300 | case ${tableNameCaps}_ID:
301 | deleteCount = dbConnection.delete(${table.getTableClassName()}Table.TABLE_NAME, ${table.getTableClassName()}Table.WHERE_ID_EQUALS, new String[] { uri.getLastPathSegment() });
302 |
303 | #if( $includedRelations )
304 | #foreach( $includedRelation in $includedRelations )
305 | #set( $joinTableName = $includedRelation.getLeftTableConstantName() + "_JOIN_" + $includedRelation.getRightTableConstantName() + "_CONTENT_URI")
306 | joinUris.add(${joinTableName});
307 |
308 | #end
309 | #end
310 | dbConnection.setTransactionSuccessful();
311 | break;
312 |
313 | #end
314 | default :
315 | throw new IllegalArgumentException("Unsupported URI:" + uri);
316 | }
317 | } finally {
318 | dbConnection.endTransaction();
319 | }
320 |
321 | if (deleteCount > 0) {
322 | getContext().getContentResolver().notifyChange(uri, null);
323 |
324 | for (Uri joinUri : joinUris) {
325 | getContext().getContentResolver().notifyChange(joinUri, null);
326 | }
327 | }
328 |
329 | return deleteCount;
330 | }
331 | }
--------------------------------------------------------------------------------
/src/main/resources/Database.vm:
--------------------------------------------------------------------------------
1 | package ${packageName}.database;
2 |
3 | import ${packageName}.database.table.*;
4 |
5 | import android.content.Context;
6 | import android.database.sqlite.SQLiteDatabase;
7 | import android.database.sqlite.SQLiteOpenHelper;
8 |
9 | #set( $providerNameLower = $providerName.toLowerCase() )
10 | public class ${providerName}Database extends SQLiteOpenHelper {
11 | private static final String DATABASE_NAME = "${providerNameLower}_database.db";
12 | private static final int DATABASE_VERSION = ${databaseVersion};
13 |
14 | public ${providerName}Database(final Context context) {
15 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
16 | }
17 |
18 | @Override
19 | public final void onCreate(final SQLiteDatabase db) {
20 | #foreach( $table in $tables )
21 | db.execSQL(${table.getTableClassName()}Table.SQL_CREATE);
22 |
23 | #end
24 | }
25 |
26 | @Override
27 | public final void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
28 | upgrade(db, oldVersion, newVersion);
29 | }
30 |
31 | private final void dropTablesAndCreate(final SQLiteDatabase db) {
32 | #foreach( $table in $tables )
33 | db.execSQL(${table.getTableClassName()}Table.SQL_DROP);
34 |
35 | #end
36 |
37 | onCreate(db);
38 | }
39 |
40 | private void upgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
41 | dropTablesAndCreate(db);
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | generator.ContentProviderProcessor
2 |
--------------------------------------------------------------------------------
/src/main/resources/Model.vm:
--------------------------------------------------------------------------------
1 | package ${packageName}.model;
2 |
3 | import android.content.ContentValues;
4 | import android.database.Cursor;
5 |
6 | import ${packageName}.database.table.${tableName}Table;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class ${tableName} {
12 | private long mRowId;
13 | #foreach( $field in $fields )
14 | #if($field)
15 | private ${field.getJavaTypeString()} ${field.getPrivateVariableName()};
16 | #end
17 | #end
18 |
19 | #if( $participatingRelationships )
20 | #foreach( $relationship in $participatingRelationships )
21 | #set( $foreignKey = $relationship.getForeignKeyPrivateVariableNameForTable($table) )
22 | #if( $foreignKey )
23 | private Long ${foreignKey};
24 | #end
25 | #end
26 | #end
27 |
28 | private ContentValues mValues = new ContentValues();
29 |
30 | public ${tableName}() {}
31 |
32 | public ${tableName}(final Cursor cursor) {
33 | this(cursor, false);
34 | }
35 |
36 | public ${tableName}(final Cursor cursor, boolean prependTableName) {
37 | String prefix = prependTableName ? ${tableName}Table.TABLE_NAME + "_" : "";
38 | setRowId(cursor.getLong(cursor.getColumnIndex(prefix + ${tableName}Table._ID)));
39 | #foreach ($field in $fields)
40 | #if($field)
41 | set${field.getNameAsTitleCase()}(cursor.${field.getJavaTypeStringGetter()}(cursor.getColumnIndex(prefix + ${tableName}Table.${field.getConstantString()})));
42 | #end
43 | #end
44 |
45 | #if( $participatingRelationships )
46 | #foreach( $relationship in $participatingRelationships )
47 | #set( $foreignKeyTitleCase = $relationship.getForeignKeyVariableAsTitleCase($table) )
48 | #set( $foreignKeyConstant = $relationship.getForeignKeyNameForTable($table) )
49 | #if( $foreignKeyTitleCase && $foreignKeyConstant)
50 | set${foreignKeyTitleCase}(cursor.getLong(cursor.getColumnIndex(prefix + ${tableName}Table.${foreignKeyConstant})));
51 | #end
52 | #end
53 | #end
54 | }
55 |
56 | public ContentValues getContentValues() {
57 | return mValues;
58 | }
59 |
60 | public Long getRowId() {
61 | return mRowId;
62 | }
63 |
64 | public void setRowId(long _id) {
65 | mRowId = _id;
66 | mValues.put(${tableName}Table._ID, _id);
67 | }
68 | #foreach ($field in $fields)
69 | #if($field)
70 | public void set${field.getNameAsTitleCase()}(${field.getJavaTypeString()} ${field.getFieldName()}) {
71 | ${field.getPrivateVariableName()} = ${field.getFieldName()};
72 | mValues.put(${tableName}Table.${field.getConstantString()}, ${field.getFieldName()});
73 | }
74 |
75 | public ${field.getJavaTypeString()} get${field.getNameAsTitleCase()}() {
76 | return ${field.getPrivateVariableName()};
77 | }
78 |
79 |
80 | #end
81 | #end
82 |
83 | #if( $participatingRelationships )
84 | #foreach( $relationship in $participatingRelationships )
85 | #set( $foreignKeyTitleCase = $relationship.getForeignKeyVariableAsTitleCase($table) )
86 | #set( $foreignKeyVariableName = $relationship.getForeignKeyVariableNameForTable($table) )
87 | #set( $foreignKeyPrivateVariableName = $relationship.getForeignKeyPrivateVariableNameForTable($table) )
88 | #set( $foreignKeyConstant = $relationship.getForeignKeyNameForTable($table) )
89 | #if( $foreignKeyTitleCase )
90 | public void set${foreignKeyTitleCase}(Long ${foreignKeyVariableName}) {
91 | ${foreignKeyPrivateVariableName} = ${foreignKeyVariableName};
92 | mValues.put(${tableName}Table.${foreignKeyConstant}, ${foreignKeyVariableName});
93 | }
94 |
95 | public Long get${foreignKeyTitleCase}() {
96 | return ${foreignKeyPrivateVariableName};
97 | }
98 |
99 | #end
100 | #end
101 | #end
102 |
103 | public static List<${tableName}> listFromCursor(Cursor cursor) {
104 | List<${tableName}> list = new ArrayList<${tableName}>();
105 |
106 | if (cursor != null && cursor.moveToFirst()) {
107 | do {
108 | list.add(new ${tableName}(cursor));
109 | } while (cursor.moveToNext());
110 | }
111 |
112 | return list;
113 | }
114 | }
--------------------------------------------------------------------------------
/src/main/resources/ProviderXML.vm:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/Table.vm:
--------------------------------------------------------------------------------
1 | package ${packageName}.database.table;
2 |
3 | public interface ${tableName}Table {
4 | #set( $tableNameLower = $tableName.toLowerCase() )
5 | String TABLE_NAME = "${tableNameLower}";
6 |
7 | String _ID = "_id";
8 |
9 | #set ($allColumns = "_ID")
10 | #set ($tableCreate = "_id INTEGER PRIMARY KEY AUTOINCREMENT")
11 | #foreach( $field in $fields )
12 | #if ($field)
13 | String ${field.getConstantString()} = "${field.getConstantString().toLowerCase()}";
14 | #set ($allColumns = $allColumns + ", " + $field.getConstantString())
15 | #set ($tableCreate = $tableCreate + ", " + $field.getConstantString().toLowerCase() + " " + $field.getTypeString())
16 | #if (!$insertFields)
17 | #set ($insertFields = $field.getConstantString().toLowerCase())
18 | #set ($escapeFields = "?")
19 | #else
20 | #set ($insertFields = $insertFields + ", " + $field.getConstantString().toLowerCase())
21 | #set ($escapeFields = $escapeFields + ", ?")
22 | #end
23 | #end
24 | #end
25 | #if( $participatingRelationships )
26 | #foreach( $relationship in $participatingRelationships )
27 | #set( $foreignKey = $relationship.getForeignKeyNameForTable($table) )
28 | #if( $foreignKey )
29 | String ${foreignKey} = "${foreignKey.toLowerCase()}";
30 | #set ($insertFields = $insertFields + ", " + ${foreignKey.toLowerCase()})
31 | #set ($escapeFields = $escapeFields + ", ?")
32 | #set ($allColumns = $allColumns + ", " + $foreignKey)
33 | #set ($tableCreate = $tableCreate + ", " + ${foreignKey.toLowerCase()} + " INTEGER")
34 | #end
35 |
36 | #end
37 | #end
38 | String[] ALL_COLUMNS = new String[] {${allColumns}};
39 |
40 | String SQL_CREATE = "CREATE TABLE ${tableNameLower} ( ${tableCreate} )";
41 |
42 | String SQL_INSERT = "INSERT INTO ${tableNameLower} ( ${insertFields} ) VALUES ( ${escapeFields} )";
43 |
44 | String SQL_DROP = "DROP TABLE IF EXISTS ${tableNameLower}";
45 |
46 | String WHERE_ID_EQUALS = _ID + "=?";
47 |
48 | #if( $participatingRelationships )
49 | #foreach( $relationship in $participatingRelationships )
50 | #set( $foreignKey = $relationship.getForeignKeyNameForTable($table) )
51 | #if( $foreignKey )
52 | String WHERE_${foreignKey}_EQUALS = ${foreignKey} + "=?";
53 | #end
54 | #end
55 | #end
56 | }
--------------------------------------------------------------------------------
/src/main/resources/velocity.properties:
--------------------------------------------------------------------------------
1 | runtime.log.logsystem.class = org.apache.velocity.runtime.log.SystemLogChute
2 |
3 | resource.loader = classpath
4 | classpath.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
--------------------------------------------------------------------------------