Commit a3db1c4f authored by rfevang's avatar rfevang Committed by Commit bot

Introduce ItemPosition class for enhanced bookmarks.

This class will be used for setting/reading the string positions
used by the enhanced bookmarks.
BUG=411412

Review URL: https://codereview.chromium.org/510543002

Cr-Commit-Position: refs/heads/master@{#293674}
parent 2014111c
......@@ -115,6 +115,7 @@
'enhanced_bookmarks/enhanced_bookmark_utils_unittest.cc',
'enhanced_bookmarks/image_store_ios_unittest.mm',
'enhanced_bookmarks/image_store_unittest.cc',
'enhanced_bookmarks/item_position_unittest.cc',
'enhanced_bookmarks/metadata_accessor_unittest.cc',
'feedback/feedback_common_unittest.cc',
'feedback/feedback_data_unittest.cc',
......
......@@ -30,6 +30,8 @@
'enhanced_bookmarks/image_store_util.cc',
'enhanced_bookmarks/image_store_util.h',
'enhanced_bookmarks/image_store_util_ios.mm',
'enhanced_bookmarks/item_position.cc',
'enhanced_bookmarks/item_position.h',
'enhanced_bookmarks/metadata_accessor.cc',
'enhanced_bookmarks/metadata_accessor.h',
'enhanced_bookmarks/persistent_image_store.cc',
......
// Copyright 2014 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 "components/enhanced_bookmarks/item_position.h"
#include "base/logging.h"
namespace {
const unsigned kPositionBase = 64;
const char kPositionAlphabet[] =
".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
} // namespace
namespace enhanced_bookmarks {
ItemPosition::ItemPosition() {
}
ItemPosition::ItemPosition(const PositionVector& position)
: position_(position) {
}
ItemPosition::~ItemPosition() {
}
// static
ItemPosition ItemPosition::CreateInitialPosition() {
PositionVector position(1, kPositionBase / 2);
return ItemPosition(position);
}
// static
ItemPosition ItemPosition::CreateBefore(const ItemPosition& other) {
DCHECK(other.IsValid());
return ItemPosition(CreateBeforeImpl(other.position_, 0));
}
// static
ItemPosition::PositionVector ItemPosition::CreateBeforeImpl(
const PositionVector& other,
size_t from_index) {
DCHECK_LT(from_index, other.size());
PositionVector before(other.begin() + from_index, other.end());
// Decrement the last character instead of going half-way to 0 in order to
// make sure chaining CreateBefore calls result in logarithmic rather than
// linear length growth.
before[before.size() - 1] /= 2;
if (before[before.size() - 1] != 0) {
// If the last digit didn't change to 0, we're done!
return before;
}
// Reset trailing zeros, then decrement the last non-zero digit.
int index = before.size() - 1;
while (index >= 0 && before[index] == 0) {
before[index--] = kPositionBase / 2;
}
// Negative index means all digits were zeros. Put that many zeros to the
// front of the string to get one that is comes before the input given.
// This will cause the returned string to be twice as long as the input one,
// (and about twice as long as needed for a valid return value), however that
// means this function can be called many times more before we need to
// increase the string size again. Increasing it with the minimum length
// would result in a linear string size growth.
if (index < 0) {
before.insert(before.begin(), before.size(), 0);
} else {
before[index] /= 2;
}
return before;
}
// static
ItemPosition ItemPosition::CreateAfter(const ItemPosition& other) {
DCHECK(other.IsValid());
return ItemPosition(CreateAfterImpl(other.position_, 0));
}
// static
ItemPosition::PositionVector ItemPosition::CreateAfterImpl(
const PositionVector& other,
size_t from_index) {
DCHECK_LE(from_index, other.size());
if (from_index == other.size()) {
return PositionVector(1, kPositionBase / 2);
}
PositionVector after(other.begin() + from_index, other.end());
// Instead of splitting the position with infinity, increment the last digit
// possible, and reset all digits after. This makes sure chaining createAfter
// will result in a logarithmic rather than linear length growth.
size_t index = after.size() - 1;
do {
after[index] += (kPositionBase - after[index] + 1) / 2;
if (after[index] != kPositionBase)
return after;
after[index] = kPositionBase / 2;
} while (index-- > 0);
// All digits must have been at the maximal value already, so the string
// length has to increase. Double it's size to ensure CreateAfter can be
// called exponentially more times every time this needs to happen.
after.insert(after.begin(), after.size(), kPositionBase - 1);
return after;
}
// static
ItemPosition ItemPosition::CreateBetween(const ItemPosition& before,
const ItemPosition& after) {
DCHECK(before.IsValid() && after.IsValid());
return ItemPosition(CreateBetweenImpl(before.position_, after.position_));
}
// static
ItemPosition::PositionVector ItemPosition::CreateBetweenImpl(
const PositionVector& before,
const PositionVector& after) {
DCHECK(before < after);
PositionVector between;
for (size_t i = 0; i < before.size(); i++) {
if (before[i] == after[i]) {
// Add the common prefix to the return value.
between.push_back(before[i]);
continue;
}
if (before[i] < after[i] - 1) {
// Split the difference between the two characters.
between.push_back((before[i] + after[i]) / 2);
return between;
}
// The difference between before[i] and after[i] is one character. A valid
// return is that character, plus something that comes after the remaining
// characters of before.
between.push_back(before[i]);
PositionVector suffix = CreateAfterImpl(before, i + 1);
between.insert(between.end(), suffix.begin(), suffix.end());
return between;
}
// |before| must be a prefix of |after|, so return that prefix followed by
// something that comes before the remaining digits of |after|.
PositionVector suffix = CreateBeforeImpl(after, before.size());
between.insert(between.end(), suffix.begin(), suffix.end());
return between;
}
std::string ItemPosition::ToString() const {
DCHECK_GT(arraysize(kPositionAlphabet), kPositionBase);
std::string str;
str.reserve(position_.size());
for (size_t i = 0; i < position_.size(); i++) {
unsigned char val = position_[i];
CHECK_LT(val, kPositionBase);
str.push_back(kPositionAlphabet[position_[i]]);
}
return str;
}
bool ItemPosition::IsValid() const {
return !position_.empty() && position_[position_.size() - 1] != 0;
}
bool ItemPosition::Equals(const ItemPosition& other) const {
return position_ == other.position_;
}
bool ItemPosition::LessThan(const ItemPosition& other) const {
return position_ < other.position_;
}
} // namespace enhanced_bookmarks
// Copyright 2014 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.
#ifndef COMPONENTS_ENHANCED_BOOKMARKS_ITEM_POSITION_H_
#define COMPONENTS_ENHANCED_BOOKMARKS_ITEM_POSITION_H_
#include <string>
#include <vector>
namespace enhanced_bookmarks {
// Convenience class for generating string based relative ordering between
// bookmark nodes.
class ItemPosition {
public:
~ItemPosition();
// Creates a position suitable to use as a starting point.
static ItemPosition CreateInitialPosition();
// Creates positions relative to other positions.
static ItemPosition CreateBefore(const ItemPosition& other);
static ItemPosition CreateBetween(const ItemPosition& before,
const ItemPosition& after);
static ItemPosition CreateAfter(const ItemPosition& other);
bool IsValid() const;
// Returns a string representation of the position. The string representations
// of two position have the same ordering as the positions themselves when
// compared using ASCII order.
std::string ToString() const;
// Comparison functions.
bool Equals(const ItemPosition& other) const;
bool LessThan(const ItemPosition& other) const;
private:
typedef std::vector<unsigned char> PositionVector;
ItemPosition();
explicit ItemPosition(const PositionVector& position);
static PositionVector CreateBeforeImpl(const PositionVector& before,
size_t from_index);
static PositionVector CreateBetweenImpl(const PositionVector& before,
const PositionVector& after);
static PositionVector CreateAfterImpl(const PositionVector& after,
size_t from_index);
PositionVector position_;
};
} // namespace enhanced_bookmarks
#endif // COMPONENTS_ENHANCED_BOOKMARKS_ITEM_POSITION_H_
// Copyright 2014 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 "components/enhanced_bookmarks/item_position.h"
#include "testing/gtest/include/gtest/gtest.h"
using enhanced_bookmarks::ItemPosition;
namespace {
class ItemPositionTest : public testing::Test {};
TEST_F(ItemPositionTest, TestCreateBefore) {
ItemPosition current = ItemPosition::CreateInitialPosition();
for (int i = 0; i < 10000; i++) {
ItemPosition next = ItemPosition::CreateBefore(current);
EXPECT_LT(next.ToString(), current.ToString());
current = next;
}
// Make sure string lengths stay reasonable.
EXPECT_LT(current.ToString().size(), 20u);
}
TEST_F(ItemPositionTest, TestCreateAfter) {
ItemPosition current = ItemPosition::CreateInitialPosition();
for (int i = 0; i < 10000; i++) {
ItemPosition next = ItemPosition::CreateAfter(current);
EXPECT_GT(next.ToString(), current.ToString());
current = next;
}
// Make sure string lengths stay reasonable.
EXPECT_LT(current.ToString().size(), 20u);
}
TEST_F(ItemPositionTest, TestCreateBetweenLeftBias) {
ItemPosition before = ItemPosition::CreateInitialPosition();
ItemPosition after = ItemPosition::CreateAfter(before);
for (int i = 0; i < 10000; i++) {
ItemPosition next = ItemPosition::CreateBetween(before, after);
EXPECT_GT(next.ToString(), before.ToString());
EXPECT_LT(next.ToString(), after.ToString());
after = next;
}
// Make sure string lengths stay reasonable.
EXPECT_LT(after.ToString().size(), 20u);
}
TEST_F(ItemPositionTest, TestCreateBetweenRightBias) {
ItemPosition before = ItemPosition::CreateInitialPosition();
ItemPosition after = ItemPosition::CreateAfter(before);
for (int i = 0; i < 10000; i++) {
ItemPosition next = ItemPosition::CreateBetween(before, after);
EXPECT_GT(next.ToString(), before.ToString());
EXPECT_LT(next.ToString(), after.ToString());
before = next;
}
// Make sure string lengths stay reasonable.
EXPECT_LT(before.ToString().size(), 20u);
}
TEST_F(ItemPositionTest, TestCreateBetweenAlternating) {
ItemPosition before = ItemPosition::CreateInitialPosition();
ItemPosition after = ItemPosition::CreateAfter(before);
for (int i = 0; i < 1000; i++) {
ItemPosition next = ItemPosition::CreateBetween(before, after);
EXPECT_GT(next.ToString(), before.ToString());
EXPECT_LT(next.ToString(), after.ToString());
if ((i & 1) == 1)
before = next;
else
after = next;
}
// There's no way to keep the string length down for all possible insertion
// scenarios, and this one should be fairly rare in practice. Still verify
// that at least the growth is restricted to about n*log_2(kPositionBase).
EXPECT_LT(before.ToString().size(), 200u);
}
} // namespace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment