EntityDiff.java revision 2ae666ec99ae9318936a9326e5243987e4e1c586
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.contacts.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 */ 35public class EntityDiff extends ArrayList<ContentProviderOperation> { 36 private EntityDiff() { 37 } 38 39 /** 40 * Build the set of {@link ContentProviderOperation} needed to translate 41 * from "before" to "after". Tries its best to keep operations to 42 * minimal number required. Assumes that all {@link ContentValues} are 43 * keyed using {@link BaseColumns#_ID} values. 44 */ 45 public static EntityDiff buildDiff(Entity before, Entity after, Uri targetUri, 46 String childForeignKey) { 47 final EntityDiff diff = new EntityDiff(); 48 49 Builder builder; 50 ContentValues values; 51 52 if (before == null) { 53 // Before doesn't exist, so insert "after" values 54 builder = ContentProviderOperation.newInsert(targetUri); 55 builder.withValues(after.getEntityValues()); 56 diff.add(builder.build()); 57 58 for (NamedContentValues child : after.getSubValues()) { 59 // Add builder with reference to original _id when needed 60 builder = ContentProviderOperation.newInsert(child.uri); 61 builder.withValues(child.values); 62 if (childForeignKey != null) { 63 builder.withValueBackReference(childForeignKey, 0); 64 } 65 diff.add(builder.build()); 66 } 67 68 } else if (after == null) { 69 // After doesn't exist, so delete "before" values 70 for (NamedContentValues child : before.getSubValues()) { 71 builder = ContentProviderOperation.newDelete(child.uri); 72 builder.withSelection(getSelectIdClause(child.values), null); 73 diff.add(builder.build()); 74 } 75 76 builder = ContentProviderOperation.newDelete(targetUri); 77 builder.withSelection(getSelectIdClause(before.getEntityValues()), null); 78 diff.add(builder.build()); 79 80 } else { 81 // Somewhere between, so update any changed values 82 values = after.getEntityValues(); 83 if (!before.getEntityValues().equals(values)) { 84 // Top-level values changed, so update 85 builder = ContentProviderOperation.newUpdate(targetUri); 86 builder.withSelection(getSelectIdClause(values), null); 87 builder.withValues(values); 88 diff.add(builder.build()); 89 } 90 91 // Build lookup maps for children on both sides 92 final HashMap<String, NamedContentValues> beforeChildren = buildChildrenMap(before); 93 final HashMap<String, NamedContentValues> afterChildren = buildChildrenMap(after); 94 95 // Walk through "before" children looking for deletes and updates 96 for (NamedContentValues beforeChild : beforeChildren.values()) { 97 final String key = buildChildKey(beforeChild); 98 final NamedContentValues afterChild = afterChildren.get(key); 99 100 if (afterChild == null) { 101 // After child doesn't exist, so delete "before" child 102 builder = ContentProviderOperation.newDelete(beforeChild.uri); 103 builder.withSelection(getSelectIdClause(beforeChild.values), null); 104 diff.add(builder.build()); 105 } else if (!beforeChild.values.equals(afterChild.values)) { 106 // After child still exists, and is different, so update 107 values = afterChild.values; 108 builder = ContentProviderOperation.newUpdate(afterChild.uri); 109 builder.withSelection(getSelectIdClause(values), null); 110 builder.withValues(values); 111 diff.add(builder.build()); 112 } 113 114 // Remove the now-handled "after" child 115 afterChildren.remove(key); 116 } 117 118 // Walk through remaining "after" children, which are inserts 119 for (NamedContentValues afterChild : afterChildren.values()) { 120 builder = ContentProviderOperation.newInsert(afterChild.uri); 121 builder.withValues(afterChild.values); 122 diff.add(builder.build()); 123 } 124 } 125 126 return diff; 127 } 128 129 private static String buildChildKey(NamedContentValues child) { 130 return child.uri.toString() + child.values.getAsString(BaseColumns._ID); 131 } 132 133 private static String getSelectIdClause(ContentValues values) { 134 return BaseColumns._ID + "=" + values.getAsLong(BaseColumns._ID); 135 } 136 137 private static HashMap<String, NamedContentValues> buildChildrenMap(Entity entity) { 138 final ArrayList<NamedContentValues> children = entity.getSubValues(); 139 final HashMap<String, NamedContentValues> childrenMap = new HashMap<String, NamedContentValues>( 140 children.size()); 141 for (NamedContentValues child : children) { 142 final String key = buildChildKey(child); 143 childrenMap.put(key, child); 144 } 145 return childrenMap; 146 } 147} 148