blob: f9fbbd06009edb362861917e89043726b458c17a [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// CandidateList and Candidate classes to be used with Session class.
#include "session/internal/candidate_list.h"
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "base/freelist.h"
#include "base/logging.h"
#include "base/port.h"
#include "base/util.h"
namespace mozc {
namespace session {
Candidate::Candidate()
: id_(0),
attributes_(NO_ATTRIBUTES),
subcandidate_list_(NULL),
subcandidate_list_owner_(false) {}
Candidate::~Candidate() {}
void Candidate::Clear() {
id_ = 0;
attributes_ = NO_ATTRIBUTES;
if (subcandidate_list_owner_ && subcandidate_list_ != NULL) {
delete subcandidate_list_;
}
subcandidate_list_ = NULL;
subcandidate_list_owner_ = false;
}
int Candidate::id() const {
return id_;
}
void Candidate::set_id(const int id) {
id_ = id;
}
Attributes Candidate::attributes() const {
return attributes_;
}
void Candidate::add_attributes(const Attributes attributes) {
attributes_ |= attributes;
}
void Candidate::set_attributes(const Attributes attributes) {
attributes_ = attributes;
}
bool Candidate::has_attributes(const Attributes attributes) const {
return (attributes_ & attributes) == attributes;
}
bool Candidate::IsSubcandidateList() const {
return (subcandidate_list_ != NULL);
}
const CandidateList &Candidate::subcandidate_list() const {
return *subcandidate_list_;
}
CandidateList *Candidate::mutable_subcandidate_list() {
return subcandidate_list_;
}
CandidateList *Candidate::allocate_subcandidate_list(const bool rotate) {
subcandidate_list_ = new CandidateList(rotate);
subcandidate_list_owner_ = true;
return subcandidate_list_;
}
void Candidate::set_subcandidate_list(CandidateList *subcandidate_list) {
subcandidate_list_ = subcandidate_list;
}
static const size_t kDefaultPageSize = 9;
CandidateList::CandidateList(const bool rotate)
: rotate_(rotate),
page_size_(kDefaultPageSize),
focused_index_(0),
focused_(false),
candidate_pool_(new ObjectPool<Candidate>(kDefaultPageSize)),
candidates_(new vector<Candidate *>),
next_available_id_(0),
added_candidates_(new map<uint64, int>),
alternative_ids_(new map<int, int>) {
}
CandidateList::~CandidateList() {
Clear();
}
void CandidateList::Clear() {
vector<Candidate *>::iterator it;
for (it = candidates_->begin(); it != candidates_->end(); ++it) {
(*it)->Clear();
candidate_pool_->Release(*it);
}
focused_index_ = 0;
focused_ = false;
candidates_->clear();
next_available_id_ = 0;
added_candidates_->clear();
alternative_ids_->clear();
}
const Candidate &CandidateList::GetDeepestFocusedCandidate() const {
if (focused_candidate().IsSubcandidateList()) {
return focused_candidate().subcandidate_list().GetDeepestFocusedCandidate();
}
return focused_candidate();
}
void CandidateList::AddCandidate(const int id, const string &value) {
AddCandidateWithAttributes(id, value, NO_ATTRIBUTES);
}
void CandidateList::AddCandidateWithAttributes(const int id,
const string &value,
const Attributes attributes) {
if (id >= 0) {
DCHECK(id >= next_available_id_);
// If id is not for T13N candidate, update |last_added_id_|.
next_available_id_ = id + 1;
}
// If the value has already been stored in the candidate list, reuse it and
// update the alternative_ids_.
const uint64 fp = Util::Fingerprint(value);
const pair<map<uint64, int>::iterator, bool> result =
added_candidates_->insert(make_pair(fp, id));
if (!result.second) { // insertion was failed.
const int alt_id = result.first->second;
(*alternative_ids_)[id] = alt_id;
// Add attributes to the existing candidate.
for (size_t i = 0; i < size(); ++i) {
if (candidate(i).id() == alt_id) {
(*candidates_)[i]->add_attributes(attributes);
}
}
return;
}
Candidate *new_candidate = candidate_pool_->Alloc();
candidates_->push_back(new_candidate);
new_candidate->set_id(id);
new_candidate->set_attributes(attributes);
}
void CandidateList::AddSubCandidateList(CandidateList *subcandidate_list) {
Candidate *new_candidate = candidate_pool_->Alloc();
candidates_->push_back(new_candidate);
new_candidate->set_subcandidate_list(subcandidate_list);
}
CandidateList *CandidateList::AllocateSubCandidateList(const bool rotate) {
Candidate *new_candidate = candidate_pool_->Alloc();
candidates_->push_back(new_candidate);
return new_candidate->allocate_subcandidate_list(rotate);
}
void CandidateList::set_name(const string &name) {
name_ = name;
}
const string &CandidateList::name() const {
return name_;
}
void CandidateList::set_page_size(const size_t page_size) {
page_size_ = page_size;
}
size_t CandidateList::page_size() const {
return page_size_;
}
size_t CandidateList::size() const {
return candidates_->size();
}
size_t CandidateList::last_index() const {
return size() - 1;
}
const Candidate &CandidateList::focused_candidate() const {
return candidate(focused_index_);
}
const Candidate &CandidateList::candidate(const size_t index) const {
return *(*candidates_)[index];
}
bool CandidateList::focused() const {
return focused_;
}
void CandidateList::set_focused(const bool focused) {
focused_ = focused;
}
int CandidateList::focused_id() const {
// If the list does not have any candidate, 0 will be returned.
if (size() == 0) {
return 0;
}
if (focused_candidate().IsSubcandidateList()) {
return focused_candidate().subcandidate_list().focused_id();
}
return focused_candidate().id();
}
size_t CandidateList::focused_index() const {
return focused_index_;
}
int CandidateList::next_available_id() const {
int result = next_available_id_;
for (size_t i = 0; i < candidates_->size(); ++i) {
if (candidate(i).IsSubcandidateList()) {
const int sub_available_id =
candidate(i).subcandidate_list().next_available_id();
if (result < sub_available_id) {
result = sub_available_id;
}
}
}
return result;
}
void CandidateList::GetPageRange(const size_t index,
size_t *page_begin,
size_t *page_end) const {
*page_begin = index - (index % page_size_);
*page_end = min(last_index(), *page_begin + page_size_ - 1);
}
void CandidateList::MoveFirst() {
focused_index_ = 0;
}
void CandidateList::MoveLast() {
focused_index_ = last_index();
}
bool CandidateList::MoveNext() {
// If the current candidate points to subcandidate list, the focused
// candidate in the subcandidate list will be operated.
if (focused_candidate().IsSubcandidateList() &&
mutable_focused_subcandidate_list()->MoveNext()) {
return true;
}
if (IsLast(focused_index_)) {
MoveFirst();
if (!rotate_) {
// If this candidate list does not rotate, the focused candidate
// is moved to the parent candidate list.
return false;
}
} else {
++focused_index_;
}
// If the new current candidate points to subcandidate list, the
// focused candidate in the subcandidate list should be the first
// candidate.
if (focused_candidate().IsSubcandidateList()) {
mutable_focused_subcandidate_list()->MoveFirst();
}
return true;
}
bool CandidateList::MovePrev() {
// If the current candidate points to subcandidate list, the focused
// candidate in the subcandidate list will be operated.
if (focused_candidate().IsSubcandidateList() &&
mutable_focused_subcandidate_list()->MovePrev()) {
return true;
}
if (IsFirst(focused_index_)) {
MoveLast();
if (!rotate_) {
// If this candidate list does not rotate, the focused candidate
// is moved to the parent candidate list.
return false;
}
} else {
--focused_index_;
}
// If the new current candidate points to subcandidate list, the
// focused candidate in the subcandidate list should be the first
// candidate.
if (focused_candidate().IsSubcandidateList()) {
mutable_focused_subcandidate_list()->MoveLast();
}
return true;
}
bool CandidateList::MoveNextPage() {
if (focused_candidate().IsSubcandidateList() &&
mutable_focused_subcandidate_list()->MoveNextPage()) {
return true;
}
if (IsLastPage(focused_index_)) {
if (!rotate_) {
// If the current candidate is the last candidate and rotation
// should not be performed, this function does nothing and
// returns false.
return false;
}
MoveFirst();
} else {
focused_index_ += page_size_;
}
// Move the forcused index to the beginning of the page.
focused_index_ -= (focused_index_ % page_size_);
// If the new current candidate points to subcandidate list, the
// focused candidate in the subcandidate list should be the first
// candidate.
if (focused_candidate().IsSubcandidateList()) {
mutable_focused_subcandidate_list()->MoveFirst();
}
return true;
}
bool CandidateList::MovePrevPage() {
if (focused_candidate().IsSubcandidateList() &&
mutable_focused_subcandidate_list()->MovePrevPage()) {
return true;
}
if (IsFirstPage(focused_index_)) {
if (!rotate_) {
// If the current candidate is on the first page and rotation
// should not be performed, this function does nothing and
// returns false.
return false;
}
MoveLast();
} else {
// The focused index in not on the first page, so the value of
// (focused_index_ - page_size_) must be a positive value.
focused_index_ -= page_size_;
}
// Move the forcused index to the beginning of the page.
focused_index_ -= (focused_index_ % page_size_);
// If the new current candidate points to subcandidate list, the
// focused candidate in the subcandidate list should be the first
// candidate. Note the forcused index will be moved to the
// beginning of the page on page moves.
if (focused_candidate().IsSubcandidateList()) {
mutable_focused_subcandidate_list()->MoveFirst();
}
return true;
}
bool CandidateList::MoveNextAttributes(const Attributes attributes) {
// Move one candidate.
MoveNext();
return MoveToAttributes(attributes);
}
bool CandidateList::MoveToAttributes(const Attributes attributes) {
const size_t cand_size = size();
for (size_t i = 0; i < cand_size; ++i) {
// Shift the index to make the first index focused_index_.
const size_t index = (focused_index_ + i) % cand_size;
Candidate *cand = (*candidates_)[index];
// If the candidate is a subcandidate list, the subcandidate list is
// traversed recursively.
if (cand->IsSubcandidateList() &&
cand->mutable_subcandidate_list()->MoveToAttributes(attributes)) {
focused_index_ = index;
return true;
} else if (cand->has_attributes(attributes)) {
focused_index_ = index;
return true;
}
}
return false;
}
bool CandidateList::MoveToId(const int base_id) {
int id = base_id;
// If an alternative id for the base_id found, use it to avoid
// duplicated candidates.
if (alternative_ids_->find(id) != alternative_ids_->end()) {
id = (*alternative_ids_)[id];
}
// NOTE(komatsu): Although this function's order is O(N), The size
// of N is less than kMaxCandidateSize (= 200). So it would not be
// a problem.
for (size_t i = 0; i < size(); ++i) {
Candidate *cand = (*candidates_)[i];
// If the candidate is a subcandidate list, the subcandidate list is
// traversed recursively.
if (cand->IsSubcandidateList() &&
cand->mutable_subcandidate_list()->MoveToId(id)) {
focused_index_ = i;
return true;
} else if (cand->id() == id) {
focused_index_ = i;
return true;
}
}
return false;
}
bool CandidateList::MoveToPageIndex(const size_t page_index) {
size_t begin = 0;
size_t end = 0;
GetPageRange(focused_index_, &begin, &end);
if (begin + page_index > end) {
return false;
}
focused_index_ = begin + page_index;
if (focused_candidate().IsSubcandidateList()) {
mutable_focused_subcandidate_list()->MoveFirst();
}
return true;
}
CandidateList *CandidateList::mutable_focused_subcandidate_list() {
CHECK(focused_candidate().IsSubcandidateList());
return (*candidates_)[focused_index_]->mutable_subcandidate_list();
}
bool CandidateList::IsFirst(const size_t index) const {
return (index == 0);
}
bool CandidateList::IsLast(const size_t index) const {
return (index == size() - 1);
}
bool CandidateList::IsFirstPage(const size_t index) const {
return (index < page_size_);
}
bool CandidateList::IsLastPage(const size_t index) const {
size_t begin = 0;
size_t end = 0;
GetPageRange(index, &begin, &end);
return end == last_index();
}
} // namespace session
} // namespace mozc