1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.loaderapp.model;
18 
19 import android.content.ContentProviderOperation;
20 import android.content.ContentValues;
21 import android.content.Entity;
22 import android.content.ContentProviderOperation.Builder;
23 import android.content.Entity.NamedContentValues;
24 import android.net.Uri;
25 import android.provider.BaseColumns;
26 
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 
30 
31 /**
32  * Describes a set of {@link ContentProviderOperation} that need to be
33  * executed to transform a database from one {@link Entity} to another.
34  */
35 @Deprecated
36 public class EntityDiff extends ArrayList<ContentProviderOperation> {
EntityDiff()37     private EntityDiff() {
38     }
39 
40     /**
41      * Build the set of {@link ContentProviderOperation} needed to translate
42      * from "before" to "after". Tries its best to keep operations to
43      * minimal number required. Assumes that all {@link ContentValues} are
44      * keyed using {@link BaseColumns#_ID} values.
45      */
buildDiff(Entity before, Entity after, Uri targetUri, String childForeignKey)46     public static EntityDiff buildDiff(Entity before, Entity after, Uri targetUri,
47             String childForeignKey) {
48         final EntityDiff diff = new EntityDiff();
49 
50         Builder builder;
51         ContentValues values;
52 
53         if (before == null) {
54             // Before doesn't exist, so insert "after" values
55             builder = ContentProviderOperation.newInsert(targetUri);
56             builder.withValues(after.getEntityValues());
57             diff.add(builder.build());
58 
59             for (NamedContentValues child : after.getSubValues()) {
60                 // Add builder with reference to original _id when needed
61                 builder = ContentProviderOperation.newInsert(child.uri);
62                 builder.withValues(child.values);
63                 if (childForeignKey != null) {
64                     builder.withValueBackReference(childForeignKey, 0);
65                 }
66                 diff.add(builder.build());
67             }
68 
69         } else if (after == null) {
70             // After doesn't exist, so delete "before" values
71             for (NamedContentValues child : before.getSubValues()) {
72                 builder = ContentProviderOperation.newDelete(child.uri);
73                 builder.withSelection(getSelectIdClause(child.values), null);
74                 diff.add(builder.build());
75             }
76 
77             builder = ContentProviderOperation.newDelete(targetUri);
78             builder.withSelection(getSelectIdClause(before.getEntityValues()), null);
79             diff.add(builder.build());
80 
81         } else {
82             // Somewhere between, so update any changed values
83             values = after.getEntityValues();
84             if (!before.getEntityValues().equals(values)) {
85                 // Top-level values changed, so update
86                 builder = ContentProviderOperation.newUpdate(targetUri);
87                 builder.withSelection(getSelectIdClause(values), null);
88                 builder.withValues(values);
89                 diff.add(builder.build());
90             }
91 
92             // Build lookup maps for children on both sides
93             final HashMap<String, NamedContentValues> beforeChildren = buildChildrenMap(before);
94             final HashMap<String, NamedContentValues> afterChildren = buildChildrenMap(after);
95 
96             // Walk through "before" children looking for deletes and updates
97             for (NamedContentValues beforeChild : beforeChildren.values()) {
98                 final String key = buildChildKey(beforeChild);
99                 final NamedContentValues afterChild = afterChildren.get(key);
100 
101                 if (afterChild == null) {
102                     // After child doesn't exist, so delete "before" child
103                     builder = ContentProviderOperation.newDelete(beforeChild.uri);
104                     builder.withSelection(getSelectIdClause(beforeChild.values), null);
105                     diff.add(builder.build());
106                 } else if (!beforeChild.values.equals(afterChild.values)) {
107                     // After child still exists, and is different, so update
108                     values = afterChild.values;
109                     builder = ContentProviderOperation.newUpdate(afterChild.uri);
110                     builder.withSelection(getSelectIdClause(values), null);
111                     builder.withValues(values);
112                     diff.add(builder.build());
113                 }
114 
115                 // Remove the now-handled "after" child
116                 afterChildren.remove(key);
117             }
118 
119             // Walk through remaining "after" children, which are inserts
120             for (NamedContentValues afterChild : afterChildren.values()) {
121                 builder = ContentProviderOperation.newInsert(afterChild.uri);
122                 builder.withValues(afterChild.values);
123                 diff.add(builder.build());
124             }
125         }
126 
127         return diff;
128     }
129 
buildChildKey(NamedContentValues child)130     private static String buildChildKey(NamedContentValues child) {
131         return child.uri.toString() + child.values.getAsString(BaseColumns._ID);
132     }
133 
getSelectIdClause(ContentValues values)134     private static String getSelectIdClause(ContentValues values) {
135         return BaseColumns._ID + "=" + values.getAsLong(BaseColumns._ID);
136     }
137 
buildChildrenMap(Entity entity)138     private static HashMap<String, NamedContentValues> buildChildrenMap(Entity entity) {
139         final ArrayList<NamedContentValues> children = entity.getSubValues();
140         final HashMap<String, NamedContentValues> childrenMap = new HashMap<String, NamedContentValues>(
141                 children.size());
142         for (NamedContentValues child : children) {
143             final String key = buildChildKey(child);
144             childrenMap.put(key, child);
145         }
146         return childrenMap;
147     }
148 }
149