// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sync/syncable/parent_child_index.h" #include "base/stl_util.h" #include "sync/syncable/entry_kernel.h" #include "sync/syncable/syncable_id.h" namespace syncer { namespace syncable { bool ChildComparator::operator()(const EntryKernel* a, const EntryKernel* b) const { const UniquePosition& a_pos = a->ref(UNIQUE_POSITION); const UniquePosition& b_pos = b->ref(UNIQUE_POSITION); if (a_pos.IsValid() && b_pos.IsValid()) { // Position is important to this type. return a_pos.LessThan(b_pos); } else if (a_pos.IsValid() && !b_pos.IsValid()) { // TODO(rlarocque): Remove this case. // An item with valid position as sibling of one with invalid position. // We should not support this, but the tests rely on it. For now, just // move all invalid position items to the right. return true; } else if (!a_pos.IsValid() && b_pos.IsValid()) { // TODO(rlarocque): Remove this case. // Mirror of the above case. return false; } else { // Position doesn't matter. DCHECK(!a->ref(UNIQUE_POSITION).IsValid()); DCHECK(!b->ref(UNIQUE_POSITION).IsValid()); // Sort by META_HANDLE to ensure consistent order for testing. return a->ref(META_HANDLE) < b->ref(META_HANDLE); } } ParentChildIndex::ParentChildIndex() { // Pre-allocate these two vectors to the number of model types. model_type_root_ids_.resize(MODEL_TYPE_COUNT); type_root_child_sets_.resize(MODEL_TYPE_COUNT); } ParentChildIndex::~ParentChildIndex() { for (int i = 0; i < MODEL_TYPE_COUNT; i++) { // Normally all OrderedChildSet instances in |type_root_child_sets_| // are shared with |parent_children_map_|. Null out shared instances and // ScopedVector will take care of the ones that are not shared (if any). if (!model_type_root_ids_[i].IsNull()) { DCHECK_EQ(type_root_child_sets_[i], parent_children_map_.find(model_type_root_ids_[i])->second); type_root_child_sets_[i] = nullptr; } } STLDeleteContainerPairSecondPointers(parent_children_map_.begin(), parent_children_map_.end()); } bool ParentChildIndex::ShouldInclude(const EntryKernel* entry) { // This index excludes deleted items and the root item. The root // item is excluded so that it doesn't show up as a child of itself. return !entry->ref(IS_DEL) && !entry->ref(ID).IsRoot(); } bool ParentChildIndex::Insert(EntryKernel* entry) { DCHECK(ShouldInclude(entry)); OrderedChildSet* siblings = nullptr; const Id& parent_id = entry->ref(PARENT_ID); ModelType model_type = entry->GetModelType(); if (ShouldUseParentId(parent_id, model_type)) { // Hierarchical type, lookup child set in the map. DCHECK(!parent_id.IsNull()); ParentChildrenMap::iterator it = parent_children_map_.find(parent_id); if (it != parent_children_map_.end()) { siblings = it->second; } else { siblings = new OrderedChildSet(); parent_children_map_.insert(std::make_pair(parent_id, siblings)); } } else { // Non-hierarchical type, return a pre-defined collection by type. siblings = GetOrCreateModelTypeChildSet(model_type); } // If this is one of type root folder for a non-hierarchical type, associate // its ID with the model type and the type's pre-defined child set with the // type root ID. // TODO(stanisc): crbug/438313: Just TypeSupportsHierarchy condition should // theoretically be sufficient but in practice many tests don't properly // initialize entries so TypeSupportsHierarchy ends up failing. Consider // tweaking TypeSupportsHierarchy and fixing all related test code. if (parent_id.IsRoot() && entry->ref(IS_DIR) && syncer::IsRealDataType(model_type) && !TypeSupportsHierarchy(model_type)) { const Id& type_root_id = entry->ref(ID); // Disassociate the type root child set with the previous ID if any. const Id& prev_type_root_id = model_type_root_ids_[model_type]; if (!prev_type_root_id.IsNull()) { ParentChildrenMap::iterator it = parent_children_map_.find(prev_type_root_id); if (it != parent_children_map_.end()) { // If the entry exists in the map it must already have the same // model type specific child set. This child set will be re-inserted // in the map under the new ID below so it is safe to simply erase // the entry here. DCHECK_EQ(it->second, GetModelTypeChildSet(model_type)); parent_children_map_.erase(it); } } // Store the new type root ID and associate the child set. // Note that the child set isn't owned by the map in this case. model_type_root_ids_[model_type] = type_root_id; parent_children_map_.insert( std::make_pair(type_root_id, GetOrCreateModelTypeChildSet(model_type))); } // Finally, insert the entry in the child set. return siblings->insert(entry).second; } // Like the other containers used to help support the syncable::Directory, this // one does not own any EntryKernels. This function removes references to the // given EntryKernel but does not delete it. void ParentChildIndex::Remove(EntryKernel* e) { OrderedChildSet* siblings = nullptr; ModelType model_type = e->GetModelType(); const Id& parent_id = e->ref(PARENT_ID); bool should_erase = false; ParentChildrenMap::iterator it; if (ShouldUseParentId(parent_id, model_type)) { // Hierarchical type, lookup child set in the map. DCHECK(!parent_id.IsNull()); it = parent_children_map_.find(parent_id); DCHECK(it != parent_children_map_.end()); siblings = it->second; should_erase = true; } else { // Non-hierarchical type, return a pre-defined child set by type. siblings = type_root_child_sets_[model_type]; } OrderedChildSet::iterator j = siblings->find(e); DCHECK(j != siblings->end()); // Erase the entry from the child set. siblings->erase(j); // If the set is now empty and isn't shareable with |type_root_child_sets_|, // erase it from the map. if (siblings->empty() && should_erase) { delete siblings; parent_children_map_.erase(it); } } bool ParentChildIndex::Contains(EntryKernel *e) const { const OrderedChildSet* siblings = GetChildSet(e); return siblings && siblings->count(e) > 0; } const OrderedChildSet* ParentChildIndex::GetChildren(const Id& id) const { DCHECK(!id.IsNull()); ParentChildrenMap::const_iterator parent = parent_children_map_.find(id); if (parent == parent_children_map_.end()) { return nullptr; } OrderedChildSet* children = parent->second; // The expectation is that the function returns nullptr instead of an empty // child set if (children && children->empty()) children = nullptr; return children; } const OrderedChildSet* ParentChildIndex::GetChildren(EntryKernel* e) const { return GetChildren(e->ref(ID)); } const OrderedChildSet* ParentChildIndex::GetSiblings(EntryKernel* e) const { // This implies the entry is in the index. DCHECK(Contains(e)); const OrderedChildSet* siblings = GetChildSet(e); DCHECK(siblings && !siblings->empty()); return siblings; } /* static */ bool ParentChildIndex::ShouldUseParentId(const Id& parent_id, ModelType model_type) { // For compatibility with legacy unit tests, in addition to hierarchical // entries, this returns true any entries directly under root and for entries // of UNSPECIFIED model type. return parent_id.IsRoot() || TypeSupportsHierarchy(model_type) || !syncer::IsRealDataType(model_type); } const OrderedChildSet* ParentChildIndex::GetChildSet(EntryKernel* e) const { ModelType model_type = e->GetModelType(); const Id& parent_id = e->ref(PARENT_ID); if (ShouldUseParentId(parent_id, model_type)) { // Hierarchical type, lookup child set in the map. ParentChildrenMap::const_iterator it = parent_children_map_.find(parent_id); if (it == parent_children_map_.end()) return nullptr; return it->second; } // Non-hierarchical type, return a collection indexed by type. return GetModelTypeChildSet(model_type); } const OrderedChildSet* ParentChildIndex::GetModelTypeChildSet( ModelType model_type) const { return type_root_child_sets_[model_type]; } OrderedChildSet* ParentChildIndex::GetOrCreateModelTypeChildSet( ModelType model_type) { if (!type_root_child_sets_[model_type]) type_root_child_sets_[model_type] = new OrderedChildSet(); return type_root_child_sets_[model_type]; } const Id& ParentChildIndex::GetModelTypeRootId(ModelType model_type) const { return model_type_root_ids_[model_type]; } } // namespace syncable } // namespace syncer