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> { 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