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
17package com.android.loaderapp.model;
18
19import android.content.ContentProviderOperation;
20import android.content.ContentValues;
21import android.content.Entity;
22import android.content.ContentProviderOperation.Builder;
23import android.content.Entity.NamedContentValues;
24import android.net.Uri;
25import android.provider.BaseColumns;
26
27import java.util.ArrayList;
28import 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
36public class EntityDiff extends ArrayList<ContentProviderOperation> {
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     */
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
130    private static String buildChildKey(NamedContentValues child) {
131        return child.uri.toString() + child.values.getAsString(BaseColumns._ID);
132    }
133
134    private static String getSelectIdClause(ContentValues values) {
135        return BaseColumns._ID + "=" + values.getAsLong(BaseColumns._ID);
136    }
137
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