Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sync/syncable/parent_child_index.h" | 5 #include "sync/syncable/parent_child_index.h" |
| 6 | 6 |
| 7 #include "base/stl_util.h" | 7 #include "base/stl_util.h" |
| 8 | 8 |
| 9 #include "sync/syncable/entry_kernel.h" | 9 #include "sync/syncable/entry_kernel.h" |
| 10 #include "sync/syncable/syncable_id.h" | 10 #include "sync/syncable/syncable_id.h" |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 33 } else { | 33 } else { |
| 34 // Position doesn't matter. | 34 // Position doesn't matter. |
| 35 DCHECK(!a->ref(UNIQUE_POSITION).IsValid()); | 35 DCHECK(!a->ref(UNIQUE_POSITION).IsValid()); |
| 36 DCHECK(!b->ref(UNIQUE_POSITION).IsValid()); | 36 DCHECK(!b->ref(UNIQUE_POSITION).IsValid()); |
| 37 // Sort by META_HANDLE to ensure consistent order for testing. | 37 // Sort by META_HANDLE to ensure consistent order for testing. |
| 38 return a->ref(META_HANDLE) < b->ref(META_HANDLE); | 38 return a->ref(META_HANDLE) < b->ref(META_HANDLE); |
| 39 } | 39 } |
| 40 } | 40 } |
| 41 | 41 |
| 42 ParentChildIndex::ParentChildIndex() { | 42 ParentChildIndex::ParentChildIndex() { |
| 43 // Pre-allocate these two vectors to the number of model types. | |
| 44 model_type_root_ids_.resize(MODEL_TYPE_COUNT); | |
| 45 type_root_child_sets_.resize(MODEL_TYPE_COUNT); | |
| 43 } | 46 } |
| 44 | 47 |
| 45 ParentChildIndex::~ParentChildIndex() { | 48 ParentChildIndex::~ParentChildIndex() { |
| 46 STLDeleteContainerPairSecondPointers( | 49 for (int i = 0; i < MODEL_TYPE_COUNT; i++) { |
| 47 parent_children_map_.begin(), parent_children_map_.end()); | 50 // Normally all OrderedChildSet instances in |type_root_child_sets_| |
| 51 // are shared with |parent_children_map_| and will be destroyed | |
| 52 // below. The only ones that haven't been shared are those that | |
| 53 // correspond to unset IDs in |model_type_root_ids_|. | |
| 54 if (type_root_child_sets_[i]) { | |
| 55 if (model_type_root_ids_[i].IsNull()) { | |
| 56 delete type_root_child_sets_[i]; | |
|
Nicolas Zea
2015/07/10 00:12:57
Would a scoped_vector work?
stanisc
2015/07/10 04:58:51
Done.
| |
| 57 } else { | |
| 58 DCHECK(parent_children_map_.find(model_type_root_ids_[i]) != | |
| 59 parent_children_map_.end()); | |
| 60 } | |
| 61 } | |
| 62 } | |
| 63 | |
| 64 STLDeleteContainerPairSecondPointers(parent_children_map_.begin(), | |
| 65 parent_children_map_.end()); | |
| 48 } | 66 } |
| 49 | 67 |
| 50 bool ParentChildIndex::ShouldInclude(const EntryKernel* entry) { | 68 bool ParentChildIndex::ShouldInclude(const EntryKernel* entry) { |
| 51 // This index excludes deleted items and the root item. The root | 69 // This index excludes deleted items and the root item. The root |
| 52 // item is excluded so that it doesn't show up as a child of itself. | 70 // item is excluded so that it doesn't show up as a child of itself. |
| 53 return !entry->ref(IS_DEL) && !entry->ref(ID).IsRoot(); | 71 // The index also excludes items without client data e.g. the items that |
| 72 // don't have client SPECIFICS or PARENT_ID. There is no need to include | |
| 73 // server-only entries because they shouldn't show up on the client until | |
| 74 // they're applied. | |
| 75 return !entry->ref(IS_DEL) && !entry->ref(ID).IsRoot() && | |
| 76 (entry->GetModelType() != UNSPECIFIED || | |
| 77 !entry->ref(PARENT_ID).IsNull()); | |
| 54 } | 78 } |
| 55 | 79 |
| 56 bool ParentChildIndex::Insert(EntryKernel* entry) { | 80 bool ParentChildIndex::Insert(EntryKernel* entry) { |
| 57 DCHECK(ShouldInclude(entry)); | 81 DCHECK(ShouldInclude(entry)); |
| 58 | 82 |
| 59 const Id& parent_id = GetParentId(entry); | 83 OrderedChildSet* siblings = nullptr; |
| 60 // Store type root ID when inserting a type root entry. | 84 const Id& parent_id = entry->ref(PARENT_ID); |
| 61 if (parent_id.IsRoot()) { | 85 ModelType model_type = entry->GetModelType(); |
| 62 ModelType model_type = GetModelType(entry); | 86 |
| 63 // TODO(stanisc): there are some unit tests that create entries | 87 if (ShouldUseParentId(parent_id, model_type)) { |
| 64 // at the root and don't bother initializing specifics which | 88 // Hierarchical type, lookup child set in the map. |
| 65 // produces TOP_LEVEL_FOLDER type here. | 89 DCHECK(!parent_id.IsNull()); |
| 66 if (syncer::IsRealDataType(model_type)) { | 90 ParentChildrenMap::iterator it = parent_children_map_.find(parent_id); |
| 67 model_type_root_ids_[model_type] = entry->ref(ID); | 91 if (it != parent_children_map_.end()) { |
| 92 siblings = it->second; | |
| 93 } else { | |
| 94 siblings = new OrderedChildSet(); | |
| 95 parent_children_map_.insert(std::make_pair(parent_id, siblings)); | |
| 68 } | 96 } |
| 97 } else { | |
| 98 // Non-hierarchical type, return a pre-defined collection by type. | |
| 99 siblings = GetOrCreateModelTypeChildSet(model_type); | |
| 69 } | 100 } |
| 70 | 101 |
| 71 OrderedChildSet* children = NULL; | 102 // If this is one of type root folder for a non-hierarchical type, associate |
| 72 ParentChildrenMap::iterator i = parent_children_map_.find(parent_id); | 103 // its ID with the model type and the type's pre-defined child set with the |
| 73 if (i != parent_children_map_.end()) { | 104 // type root ID. |
| 74 children = i->second; | 105 // TODO(stanisc): crbug/438313: Just TypeSupportsHierarchy condition should |
| 75 } else { | 106 // theoretically be sufficient but in practice many tests don't properly |
| 76 children = new OrderedChildSet(); | 107 // initialize entries so TypeSupportsHierarchy ends up failing. Consider |
| 77 parent_children_map_.insert(std::make_pair(parent_id, children)); | 108 // tweaking TypeSupportsHierarchy and fixing all related test code. |
| 109 if (parent_id.IsRoot() && entry->ref(IS_DIR) && | |
| 110 syncer::IsRealDataType(model_type) && | |
| 111 !TypeSupportsHierarchy(model_type)) { | |
| 112 const Id& type_root_id = entry->ref(ID); | |
| 113 // Disassociate the type root child set with the previous ID if any. | |
|
Nicolas Zea
2015/07/10 00:12:57
When would this happen?
stanisc
2015/07/10 04:58:51
This happens when the type root folder ID is chang
| |
| 114 const Id& prev_type_root_id = model_type_root_ids_[model_type]; | |
| 115 if (!prev_type_root_id.IsNull()) { | |
| 116 ParentChildrenMap::iterator it = | |
| 117 parent_children_map_.find(prev_type_root_id); | |
| 118 if (it != parent_children_map_.end()) { | |
| 119 // If the entry exists in the map it must already have the same | |
| 120 // model type specific child set. This child set will be re-inserted | |
| 121 // in the map under the new ID below so it is safe to simply erase | |
| 122 // the entry here. | |
| 123 DCHECK_EQ(it->second, GetModelTypeChildSet(model_type)); | |
| 124 parent_children_map_.erase(it); | |
| 125 } | |
| 126 } | |
| 127 // Store the new type root ID and associate the child set. | |
| 128 // Note that the child set isn't owned by the map in this case. | |
| 129 model_type_root_ids_[model_type] = type_root_id; | |
| 130 parent_children_map_.insert( | |
| 131 std::make_pair(type_root_id, GetOrCreateModelTypeChildSet(model_type))); | |
| 78 } | 132 } |
| 79 | 133 |
| 80 return children->insert(entry).second; | 134 // Finally, insert the entry in the child set. |
| 135 return siblings->insert(entry).second; | |
| 81 } | 136 } |
| 82 | 137 |
| 83 // Like the other containers used to help support the syncable::Directory, this | 138 // Like the other containers used to help support the syncable::Directory, this |
| 84 // one does not own any EntryKernels. This function removes references to the | 139 // one does not own any EntryKernels. This function removes references to the |
| 85 // given EntryKernel but does not delete it. | 140 // given EntryKernel but does not delete it. |
| 86 void ParentChildIndex::Remove(EntryKernel* e) { | 141 void ParentChildIndex::Remove(EntryKernel* e) { |
| 87 const Id& parent_id = GetParentId(e); | 142 OrderedChildSet* siblings = nullptr; |
| 143 ModelType model_type = e->GetModelType(); | |
| 144 const Id& parent_id = e->ref(PARENT_ID); | |
| 145 bool should_erase = false; | |
| 146 ParentChildrenMap::iterator it; | |
| 88 | 147 |
| 89 ParentChildrenMap::iterator parent = parent_children_map_.find(parent_id); | 148 if (ShouldUseParentId(parent_id, model_type)) { |
| 90 DCHECK(parent != parent_children_map_.end()); | 149 // Hierarchical type, lookup child set in the map. |
| 150 DCHECK(!parent_id.IsNull()); | |
| 151 it = parent_children_map_.find(parent_id); | |
| 152 DCHECK(it != parent_children_map_.end()); | |
| 153 siblings = it->second; | |
| 154 should_erase = true; | |
| 155 } else { | |
| 156 // Non-hierarchical type, return a pre-defined child set by type. | |
| 157 siblings = type_root_child_sets_[model_type]; | |
| 158 } | |
| 91 | 159 |
| 92 OrderedChildSet* children = parent->second; | 160 OrderedChildSet::iterator j = siblings->find(e); |
| 93 OrderedChildSet::iterator j = children->find(e); | 161 DCHECK(j != siblings->end()); |
| 94 DCHECK(j != children->end()); | |
| 95 | 162 |
| 96 children->erase(j); | 163 // Erase the entry from the child set. |
| 97 if (children->empty()) { | 164 siblings->erase(j); |
| 98 delete children; | 165 // If the set is now empty and isn't shareable with |type_root_child_sets_|, |
| 99 parent_children_map_.erase(parent); | 166 // erase it from the map. |
| 167 if (siblings->empty() && should_erase) { | |
| 168 delete siblings; | |
| 169 parent_children_map_.erase(it); | |
| 100 } | 170 } |
| 101 } | 171 } |
| 102 | 172 |
| 103 bool ParentChildIndex::Contains(EntryKernel *e) const { | 173 bool ParentChildIndex::Contains(EntryKernel *e) const { |
| 104 const Id& parent_id = GetParentId(e); | 174 const OrderedChildSet* siblings = GetChildSet(e); |
| 105 ParentChildrenMap::const_iterator parent = | 175 return siblings && siblings->count(e) > 0; |
| 106 parent_children_map_.find(parent_id); | |
| 107 if (parent == parent_children_map_.end()) { | |
| 108 return false; | |
| 109 } | |
| 110 const OrderedChildSet* children = parent->second; | |
| 111 DCHECK(children && !children->empty()); | |
| 112 return children->count(e) > 0; | |
| 113 } | 176 } |
| 114 | 177 |
| 115 const OrderedChildSet* ParentChildIndex::GetChildren(const Id& id) const { | 178 const OrderedChildSet* ParentChildIndex::GetChildren(const Id& id) const { |
| 116 DCHECK(!id.IsNull()); | 179 DCHECK(!id.IsNull()); |
| 117 | 180 |
| 118 ParentChildrenMap::const_iterator parent = parent_children_map_.find(id); | 181 ParentChildrenMap::const_iterator parent = parent_children_map_.find(id); |
| 119 if (parent == parent_children_map_.end()) { | 182 if (parent == parent_children_map_.end()) { |
| 120 return nullptr; | 183 return nullptr; |
| 121 } | 184 } |
| 122 | 185 |
| 123 // A successful lookup implies at least some children exist. | 186 OrderedChildSet* children = parent->second; |
| 124 DCHECK(!parent->second->empty()); | 187 // The expectation is that the function returns nullptr instead of an empty |
| 125 return parent->second; | 188 // child set |
| 189 if (children && children->empty()) | |
| 190 children = nullptr; | |
| 191 return children; | |
| 126 } | 192 } |
| 127 | 193 |
| 128 const OrderedChildSet* ParentChildIndex::GetChildren(EntryKernel* e) const { | 194 const OrderedChildSet* ParentChildIndex::GetChildren(EntryKernel* e) const { |
| 129 return GetChildren(e->ref(ID)); | 195 return GetChildren(e->ref(ID)); |
| 130 } | 196 } |
| 131 | 197 |
| 132 const OrderedChildSet* ParentChildIndex::GetSiblings(EntryKernel* e) const { | 198 const OrderedChildSet* ParentChildIndex::GetSiblings(EntryKernel* e) const { |
| 199 // This implies the entry is in the index. | |
| 133 DCHECK(Contains(e)); | 200 DCHECK(Contains(e)); |
| 134 const OrderedChildSet* siblings = GetChildren(GetParentId(e)); | 201 const OrderedChildSet* siblings = GetChildSet(e); |
| 135 DCHECK(siblings && !siblings->empty()); | 202 DCHECK(siblings && !siblings->empty()); |
| 136 return siblings; | 203 return siblings; |
| 137 } | 204 } |
| 138 | 205 |
| 139 const Id& ParentChildIndex::GetParentId(EntryKernel* e) const { | 206 /* static */ |
| 140 const Id& parent_id = e->ref(PARENT_ID); | 207 bool ParentChildIndex::ShouldUseParentId(const Id& parent_id, |
| 141 if (!parent_id.IsNull()) { | 208 ModelType model_type) { |
| 142 return parent_id; | 209 // Returns true for compatibility with legacy unit tests. |
|
Nicolas Zea
2015/07/10 00:12:57
I'm not sure I understand this comment? Returns tr
stanisc
2015/07/10 04:58:50
Didn't finish the comment. Updated.
| |
| 143 } | 210 return parent_id.IsRoot() || TypeSupportsHierarchy(model_type) || |
| 144 return GetModelTypeRootId(GetModelType(e)); | 211 !syncer::IsRealDataType(model_type); |
| 145 } | 212 } |
| 146 | 213 |
| 147 ModelType ParentChildIndex::GetModelType(EntryKernel* e) { | 214 const OrderedChildSet* ParentChildIndex::GetChildSet(EntryKernel* e) const { |
| 148 // TODO(stanisc): is there a more effective way to find out model type? | |
| 149 ModelType model_type = e->GetModelType(); | 215 ModelType model_type = e->GetModelType(); |
| 150 if (!syncer::IsRealDataType(model_type)) { | 216 |
| 151 model_type = e->GetServerModelType(); | 217 const Id& parent_id = e->ref(PARENT_ID); |
| 218 if (ShouldUseParentId(parent_id, model_type)) { | |
| 219 // Hierarchical type, lookup child set in the map. | |
| 220 ParentChildrenMap::const_iterator it = parent_children_map_.find(parent_id); | |
| 221 if (it != parent_children_map_.end()) { | |
| 222 return it->second; | |
| 223 } | |
| 224 } else { | |
| 225 // Non-hierarchical type, return a collection indexed by type. | |
| 226 return GetModelTypeChildSet(model_type); | |
| 152 } | 227 } |
| 153 return model_type; | 228 |
| 229 return nullptr; | |
|
Nicolas Zea
2015/07/10 00:12:57
nit: how about rewriting this as:
if (ShouldUsePa
stanisc
2015/07/10 04:58:50
Done.
| |
| 230 } | |
| 231 | |
| 232 const OrderedChildSet* ParentChildIndex::GetModelTypeChildSet( | |
| 233 ModelType model_type) const { | |
| 234 return type_root_child_sets_[model_type]; | |
| 235 } | |
| 236 | |
| 237 OrderedChildSet* ParentChildIndex::GetOrCreateModelTypeChildSet( | |
| 238 ModelType model_type) { | |
| 239 if (!type_root_child_sets_[model_type]) | |
| 240 type_root_child_sets_[model_type] = new OrderedChildSet(); | |
| 241 return type_root_child_sets_[model_type]; | |
| 154 } | 242 } |
| 155 | 243 |
| 156 const Id& ParentChildIndex::GetModelTypeRootId(ModelType model_type) const { | 244 const Id& ParentChildIndex::GetModelTypeRootId(ModelType model_type) const { |
| 157 // TODO(stanisc): Review whether this approach is reliable enough. | |
| 158 // Should this method simply enumerate children of root node ("r") instead? | |
| 159 return model_type_root_ids_[model_type]; | 245 return model_type_root_ids_[model_type]; |
| 160 } | 246 } |
| 161 | 247 |
| 162 } // namespace syncable | 248 } // namespace syncable |
| 163 } // namespace syncer | 249 } // namespace syncer |
| OLD | NEW |