1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 // Trie table for Romaji (or Kana) conversion
31
32 #include "composer/table.h"
33
34 #include <istream> // NOLINT
35 #include <map>
36 #include <memory>
37 #include <sstream>
38 #include <string>
39 #include <utility>
40
41 #include "base/config_file_stream.h"
42 #include "base/file_stream.h"
43 #include "base/hash.h"
44 #include "base/logging.h"
45 #include "base/port.h"
46 #include "base/trie.h"
47 #include "base/util.h"
48 #include "composer/internal/typing_model.h"
49 #include "config/config_handler.h"
50 #include "protocol/commands.pb.h"
51 #include "protocol/config.pb.h"
52
53 namespace mozc {
54 namespace composer {
55 namespace {
56 const char kDefaultPreeditTableFile[] = "system://romanji-hiragana.tsv";
57 const char kRomajiPreeditTableFile[] = "system://romanji-hiragana.tsv";
58 // Table for Kana combinations like "か゛" → "が".
59 const char kKanaCombinationTableFile[] = "system://kana.tsv";
60
61 // Special tables for 12keys
62 const char k12keysHiraganaTableFile[] = "system://12keys-hiragana.tsv";
63 const char k12keysHalfwidthasciiTableFile[]
64 = "system://12keys-halfwidthascii.tsv";
65 const char kFlickHiraganaTableFile[] = "system://flick-hiragana.tsv";
66 const char kFlickHalfwidthasciiTableFile[]
67 = "system://flick-halfwidthascii.tsv";
68 const char kToggleFlickHiraganaTableFile[]
69 = "system://toggle_flick-hiragana.tsv";
70 const char kToggleFlickHalfwidthasciiTableFile[]
71 = "system://toggle_flick-halfwidthascii.tsv";
72 // Special tables for QWERTY mobile
73 const char kQwertyMobileHiraganaTableFile[]
74 = "system://qwerty_mobile-hiragana.tsv";
75 const char kQwertyMobileHalfwidthasciiTableFile[]
76 = "system://qwerty_mobile-halfwidthascii.tsv";
77 // Special tables for Godan
78 const char kGodanHiraganaTableFile[] = "system://godan-hiragana.tsv";
79 const char kNotouchHiraganaTableFile[] = "system://notouch-hiragana.tsv";
80 // Reuse qwerty_mobile-halfwidthascii table
81 const char kNotouchHalfwidthasciiTableFile[]
82 = "system://qwerty_mobile-halfwidthascii.tsv";
83
84 const char kNewChunkPrefix[] = "\t";
85 const char kSpecialKeyOpen[] = "\x0F"; // Shift-In of ASCII
86 const char kSpecialKeyClose[] = "\x0E"; // Shift-Out of ASCII
87 } // namespace
88
89 // ========================================
90 // Entry
91 // ========================================
Entry(const string & input,const string & result,const string & pending,const TableAttributes attributes)92 Entry::Entry(const string &input,
93 const string &result,
94 const string &pending,
95 const TableAttributes attributes)
96 : input_(input),
97 result_(result),
98 pending_(pending),
99 attributes_(attributes) {}
100
101 // ========================================
102 // Table
103 // ========================================
Table()104 Table::Table()
105 : entries_(new EntryTrie),
106 case_sensitive_(false) {}
107
~Table()108 Table::~Table() {
109 ResetEntrySet();
110 }
111
112 static const char kKuten[] = "、";
113 static const char kTouten[] = "。";
114 static const char kComma[] = ",";
115 static const char kPeriod[] = ".";
116
117 static const char kCornerOpen[] = "「";
118 static const char kCornerClose[] = "」";
119 static const char kSlash[] = "/";
120 static const char kSquareOpen[] = "[";
121 static const char kSquareClose[] = "]";
122 static const char kMiddleDot[] = "・";
123
InitializeWithRequestAndConfig(const commands::Request & request,const config::Config & config,const DataManagerInterface & data_manager)124 bool Table::InitializeWithRequestAndConfig(
125 const commands::Request &request,
126 const config::Config &config,
127 const DataManagerInterface& data_manager) {
128 case_sensitive_ = false;
129 bool result = false;
130 typing_model_ = TypingModel::CreateTypingModel(
131 request.special_romanji_table(), data_manager);
132 if (request.special_romanji_table()
133 != mozc::commands::Request::DEFAULT_TABLE) {
134 const char *table_file_name;
135 switch (request.special_romanji_table()) {
136 case mozc::commands::Request::TWELVE_KEYS_TO_HIRAGANA:
137 table_file_name = k12keysHiraganaTableFile;
138 break;
139 case mozc::commands::Request::TWELVE_KEYS_TO_HALFWIDTHASCII:
140 table_file_name = k12keysHalfwidthasciiTableFile;
141 break;
142 case mozc::commands::Request::FLICK_TO_HIRAGANA:
143 table_file_name = kFlickHiraganaTableFile;
144 break;
145 case mozc::commands::Request::FLICK_TO_HALFWIDTHASCII:
146 table_file_name = kFlickHalfwidthasciiTableFile;
147 break;
148 case mozc::commands::Request::TOGGLE_FLICK_TO_HIRAGANA:
149 table_file_name = kToggleFlickHiraganaTableFile;
150 break;
151 case mozc::commands::Request::TOGGLE_FLICK_TO_HALFWIDTHASCII:
152 table_file_name = kToggleFlickHalfwidthasciiTableFile;
153 break;
154 case mozc::commands::Request::QWERTY_MOBILE_TO_HIRAGANA:
155 // This table is almost as same as "romaji-hiragana.tsv",
156 // and the diff should be only the behavior of ','.
157 // So probably we'd like to share the table, but we keep this way
158 // for now, as this is still internal code.
159 // TODO(hidehiko): refactor this code to clean up.
160 table_file_name = kQwertyMobileHiraganaTableFile;
161 break;
162 case mozc::commands::Request::QWERTY_MOBILE_TO_HALFWIDTHASCII:
163 table_file_name = kQwertyMobileHalfwidthasciiTableFile;
164 break;
165 case mozc::commands::Request::GODAN_TO_HIRAGANA:
166 table_file_name = kGodanHiraganaTableFile;
167 break;
168 case mozc::commands::Request::NOTOUCH_TO_HIRAGANA:
169 table_file_name = kNotouchHiraganaTableFile;
170 break;
171 case mozc::commands::Request::NOTOUCH_TO_HALFWIDTHASCII:
172 table_file_name = kNotouchHalfwidthasciiTableFile;
173 break;
174 default:
175 table_file_name = NULL;
176 }
177 if (table_file_name && LoadFromFile(table_file_name)) {
178 return true;
179 }
180 }
181 switch (config.preedit_method()) {
182 case config::Config::ROMAN:
183 result = (config.has_custom_roman_table() &&
184 !config.custom_roman_table().empty()) ?
185 LoadFromString(config.custom_roman_table()) :
186 LoadFromFile(kRomajiPreeditTableFile);
187 break;
188 case config::Config::KANA:
189 result = LoadFromFile(kRomajiPreeditTableFile);
190 break;
191 default:
192 LOG(ERROR) << "Unkonwn preedit method: " << config.preedit_method();
193 break;
194 }
195
196 if (!result) {
197 result = LoadFromFile(kDefaultPreeditTableFile);
198 if (!result) {
199 return false;
200 }
201 }
202
203 // Initialize punctuations.
204 const config::Config::PunctuationMethod punctuation_method =
205 config.punctuation_method();
206 const mozc::composer::Entry *entry = NULL;
207
208 // Comma / Kuten
209 entry = LookUp(",");
210 if (entry == NULL ||
211 (entry->result() == kKuten && entry->pending().empty())) {
212 if (punctuation_method == config::Config::COMMA_PERIOD ||
213 punctuation_method == config::Config::COMMA_TOUTEN) {
214 AddRule(",", kComma, "");
215 } else {
216 AddRule(",", kKuten, "");
217 }
218 }
219
220 // Period / Touten
221 entry = LookUp(".");
222 if (entry == NULL ||
223 (entry->result() == kTouten && entry->pending().empty())) {
224 if (punctuation_method == config::Config::COMMA_PERIOD ||
225 punctuation_method == config::Config::KUTEN_PERIOD) {
226 AddRule(".", kPeriod, "");
227 } else {
228 AddRule(".", kTouten, "");
229 }
230 }
231
232 // Initialize symbols.
233 const config::Config::SymbolMethod symbol_method = config.symbol_method();
234
235 // Slash / Middle dot
236 entry = LookUp("/");
237 if (entry == NULL ||
238 (entry->result() == kMiddleDot && entry->pending().empty())) {
239 if (symbol_method == config::Config::SQUARE_BRACKET_SLASH ||
240 symbol_method == config::Config::CORNER_BRACKET_SLASH) {
241 AddRule("/", kSlash, "");
242 } else {
243 AddRule("/", kMiddleDot, "");
244 }
245 }
246
247 // Square open bracket / Corner open bracket
248 entry = LookUp("[");
249 if (entry == NULL ||
250 (entry->result() == kCornerOpen && entry->pending().empty())) {
251 if (symbol_method == config::Config::CORNER_BRACKET_MIDDLE_DOT ||
252 symbol_method == config::Config::CORNER_BRACKET_SLASH) {
253 AddRule("[", kCornerOpen, "");
254 } else {
255 AddRule("[", kSquareOpen, "");
256 }
257 }
258
259 // Square close bracket / Corner close bracket
260 entry = LookUp("]");
261 if (entry == NULL ||
262 (entry->result() == kCornerClose && entry->pending().empty())) {
263 if (symbol_method == config::Config::CORNER_BRACKET_MIDDLE_DOT ||
264 symbol_method == config::Config::CORNER_BRACKET_SLASH) {
265 AddRule("]", kCornerClose, "");
266 } else {
267 AddRule("]", kSquareClose, "");
268 }
269 }
270
271 // result should be true here.
272 CHECK(result);
273
274 // Load Kana combination rules.
275 result = LoadFromFile(kKanaCombinationTableFile);
276 return result;
277 }
278
IsLoopingEntry(const string & input,const string & pending) const279 bool Table::IsLoopingEntry(const string &input,
280 const string &pending) const {
281 if (input.empty() || pending.empty()) {
282 return false;
283 }
284
285 string key = pending;
286 do {
287 // If input is a prefix of key, it should be looping.
288 // (ex. input="a", pending="abc").
289 if (Util::StartsWith(key, input)) {
290 return true;
291 }
292
293 size_t key_length = 0;
294 bool fixed = false;
295 const Entry *entry = LookUpPrefix(key, &key_length, &fixed);
296 if (entry == NULL) {
297 return false;
298 }
299 DCHECK_LE(key_length, key.size());
300 key = entry->pending() + key.substr(key_length);
301 } while (!key.empty());
302
303 return false;
304 }
305
AddRule(const string & input,const string & output,const string & pending)306 const Entry *Table::AddRule(const string &input,
307 const string &output,
308 const string &pending) {
309 return AddRuleWithAttributes(input, output, pending, NO_TABLE_ATTRIBUTE);
310 }
311
AddRuleWithAttributes(const string & escaped_input,const string & output,const string & escaped_pending,const TableAttributes attributes)312 const Entry *Table::AddRuleWithAttributes(const string &escaped_input,
313 const string &output,
314 const string &escaped_pending,
315 const TableAttributes attributes) {
316 if (attributes & NEW_CHUNK) {
317 // TODO(komatsu): Make a new trie tree for checking the new chunk
318 // attribute rather than reusing the conversion trie.
319 const string additional_input = kNewChunkPrefix + escaped_input;
320 AddRuleWithAttributes(additional_input, output, escaped_pending,
321 NO_TABLE_ATTRIBUTE);
322 }
323
324 const size_t kMaxSize = 300;
325 if (escaped_input.size() >= kMaxSize ||
326 output.size() >= kMaxSize ||
327 escaped_pending.size() >= kMaxSize) {
328 LOG(ERROR) << "Invalid input/output/pending";
329 return NULL;
330 }
331
332 const string input = ParseSpecialKey(escaped_input);
333 const string pending = ParseSpecialKey(escaped_pending);
334 if (IsLoopingEntry(input, pending)) {
335 LOG(WARNING) << "Entry "
336 << input << " " << output << " " << pending
337 << " is removed, since the rule is looping";
338 return NULL;
339 }
340
341 const Entry *old_entry = NULL;
342 if (entries_->LookUp(input, &old_entry)) {
343 DeleteEntry(old_entry);
344 }
345
346 Entry *entry = new Entry(input, output, pending, attributes);
347 entries_->AddEntry(input, entry);
348 entry_set_.insert(entry);
349
350 // Check if the input has a large captal character.
351 // Invisible character is exception.
352 if (!case_sensitive_) {
353 const string trimed_input = DeleteSpecialKey(input);
354 for (ConstChar32Iterator iter(trimed_input); !iter.Done(); iter.Next()) {
355 const char32 ucs4 = iter.Get();
356 if ('A' <= ucs4 && ucs4 <= 'Z') {
357 case_sensitive_ = true;
358 break;
359 }
360 }
361 }
362 return entry;
363 }
364
DeleteRule(const string & input)365 void Table::DeleteRule(const string &input) {
366 // NOTE : If this method is called and an entry is deleted,
367 // case_sensitive_ turns to be invalid
368 // because it is not updated.
369 // Currently updating logic is omitted because;
370 // - Updating needs some complex implementation.
371 // - This method is not used.
372 // - This method has no tests.
373 // - This method is private scope.
374 const Entry *old_entry;
375 if (entries_->LookUp(input, &old_entry)) {
376 DeleteEntry(old_entry);
377 }
378 entries_->DeleteEntry(input);
379 }
380
LoadFromString(const string & str)381 bool Table::LoadFromString(const string &str) {
382 std::istringstream is(str);
383 return LoadFromStream(&is);
384 }
385
LoadFromFile(const char * filepath)386 bool Table::LoadFromFile(const char *filepath) {
387 std::unique_ptr<std::istream> ifs(ConfigFileStream::LegacyOpen(filepath));
388 if (ifs.get() == NULL) {
389 return false;
390 }
391 return LoadFromStream(ifs.get());
392 }
393
typing_model() const394 const TypingModel* Table::typing_model() const {
395 return typing_model_.get();
396 }
397
398 namespace {
399 const char kAttributeDelimiter[] = " ";
400
ParseAttributes(const string & input)401 TableAttributes ParseAttributes(const string &input) {
402 TableAttributes attributes = NO_TABLE_ATTRIBUTE;
403
404 std::vector<string> attribute_strings;
405 Util::SplitStringAllowEmpty(input, kAttributeDelimiter, &attribute_strings);
406
407 for (size_t i = 0; i < attribute_strings.size(); ++i) {
408 if (attribute_strings[i] == "NewChunk") {
409 attributes |= NEW_CHUNK;
410 } else if (attribute_strings[i] == "NoTransliteration") {
411 attributes |= NO_TRANSLITERATION;
412 } else if (attribute_strings[i] == "DirectInput") {
413 attributes |= DIRECT_INPUT;
414 } else if (attribute_strings[i] == "EndChunk") {
415 attributes |= END_CHUNK;
416 }
417 }
418 return attributes;
419 }
420 } // namespace
421
LoadFromStream(std::istream * is)422 bool Table::LoadFromStream(std::istream *is) {
423 DCHECK(is);
424 string line;
425 const string empty_pending("");
426 while (!is->eof()) {
427 getline(*is, line);
428 Util::ChopReturns(&line);
429 if (line.empty()) {
430 continue;
431 }
432
433 std::vector<string> rules;
434 Util::SplitStringAllowEmpty(line, "\t", &rules);
435 if (rules.size() == 4) {
436 const TableAttributes attributes = ParseAttributes(rules[3]);
437 AddRuleWithAttributes(rules[0], rules[1], rules[2], attributes);
438 } else if (rules.size() == 3) {
439 AddRule(rules[0], rules[1], rules[2]);
440 } else if (rules.size() == 2) {
441 AddRule(rules[0], rules[1], empty_pending);
442 } else {
443 if (line[0] != '#') {
444 LOG(ERROR) << "Format error: " << line;
445 }
446 continue;
447 }
448 }
449
450 return true;
451 }
452
LookUp(const string & input) const453 const Entry *Table::LookUp(const string &input) const {
454 const Entry *entry = NULL;
455 if (case_sensitive_) {
456 entries_->LookUp(input, &entry);
457 } else {
458 string normalized_input = input;
459 Util::LowerString(&normalized_input);
460 entries_->LookUp(normalized_input, &entry);
461 }
462 return entry;
463 }
464
LookUpPrefix(const string & input,size_t * key_length,bool * fixed) const465 const Entry *Table::LookUpPrefix(const string &input,
466 size_t *key_length,
467 bool *fixed) const {
468 const Entry *entry = NULL;
469 if (case_sensitive_) {
470 entries_->LookUpPrefix(input, &entry, key_length, fixed);
471 } else {
472 string normalized_input = input;
473 Util::LowerString(&normalized_input);
474 entries_->LookUpPrefix(normalized_input, &entry, key_length, fixed);
475 }
476 return entry;
477 }
478
LookUpPredictiveAll(const string & input,std::vector<const Entry * > * results) const479 void Table::LookUpPredictiveAll(const string &input,
480 std::vector<const Entry *> *results) const {
481 if (case_sensitive_) {
482 entries_->LookUpPredictiveAll(input, results);
483 } else {
484 string normalized_input = input;
485 Util::LowerString(&normalized_input);
486 entries_->LookUpPredictiveAll(normalized_input, results);
487 }
488 }
489
HasNewChunkEntry(const string & input) const490 bool Table::HasNewChunkEntry(const string &input) const {
491 if (input.empty()) {
492 return false;
493 }
494
495 const string key = kNewChunkPrefix + input;
496 size_t key_length = 0;
497 bool fixed = false;
498 LookUpPrefix(key, &key_length, &fixed);
499 if (key_length > 1) {
500 return true;
501 }
502
503 return false;
504 }
505
HasSubRules(const string & input) const506 bool Table::HasSubRules(const string &input) const {
507 if (case_sensitive_) {
508 return entries_->HasSubTrie(input);
509 } else {
510 string normalized_input = input;
511 Util::LowerString(&normalized_input);
512 return entries_->HasSubTrie(normalized_input);
513 }
514 }
515
DeleteEntry(const Entry * entry)516 void Table::DeleteEntry(const Entry *entry) {
517 entry_set_.erase(entry);
518 delete entry;
519 }
520
ResetEntrySet()521 void Table::ResetEntrySet() {
522 for (EntrySet::iterator it = entry_set_.begin(); it != entry_set_.end();
523 ++it) {
524 delete *it;
525 }
526 entry_set_.clear();
527 }
528
case_sensitive() const529 bool Table::case_sensitive() const {
530 return case_sensitive_;
531 }
532
set_case_sensitive(const bool case_sensitive)533 void Table::set_case_sensitive(const bool case_sensitive) {
534 case_sensitive_ = case_sensitive;
535 }
536
537 namespace {
FindBlock(const string & input,const string & open,const string & close,const size_t offset,size_t * open_pos,size_t * close_pos)538 bool FindBlock(const string &input, const string &open, const string &close,
539 const size_t offset, size_t *open_pos, size_t *close_pos) {
540 DCHECK(open_pos);
541 DCHECK(close_pos);
542
543 *open_pos = input.find(open, offset);
544 if (*open_pos == string::npos) {
545 return false;
546 }
547
548 *close_pos = input.find(close, *open_pos);
549 if (*close_pos == string::npos) {
550 return false;
551 }
552
553 return true;
554 }
555 } // namespace
556
557 // static
ParseSpecialKey(const string & input)558 string Table::ParseSpecialKey(const string &input) {
559 string output;
560 size_t open_pos = 0;
561 size_t close_pos = 0;
562 for (size_t cursor = 0; cursor < input.size();) {
563 if (!FindBlock(input, "{", "}", cursor, &open_pos, &close_pos)) {
564 output.append(input, cursor, input.size() - cursor);
565 break;
566 }
567
568 output.append(input, cursor, open_pos - cursor);
569
570 // The both sizes of "{" and "}" is 1.
571 const string key(input, open_pos + 1, close_pos - open_pos - 1);
572 if (key == "{") {
573 // "{{}" is treated as "{".
574 output.append("{");
575 } else {
576 // "{abc}" is replaced with "<kSpecialKeyOpen>abc<kSpecialKeyClose>".
577 output.append(kSpecialKeyOpen);
578 output.append(key);
579 output.append(kSpecialKeyClose);
580 }
581
582 cursor = close_pos + 1;
583 }
584 return output;
585 }
586
587 // static
DeleteSpecialKey(const string & input)588 string Table::DeleteSpecialKey(const string &input) {
589 string output;
590 size_t open_pos = 0;
591 size_t close_pos = 0;
592 for (size_t cursor = 0; cursor < input.size();) {
593 if (!FindBlock(input, kSpecialKeyOpen, kSpecialKeyClose,
594 cursor, &open_pos, &close_pos)) {
595 output.append(input, cursor, input.size() - cursor);
596 break;
597 }
598
599 output.append(input, cursor, open_pos - cursor);
600 // The size of kSpecialKeyClose is 1.
601 cursor = close_pos + 1;
602 }
603 return output;
604 }
605
606 // ========================================
607 // TableContainer
608 // ========================================
TableManager()609 TableManager::TableManager()
610 : custom_roman_table_fingerprint_(Hash::Fingerprint32("")) {
611 }
612
613 TableManager::~TableManager() = default;
614
GetTable(const mozc::commands::Request & request,const mozc::config::Config & config,const mozc::DataManagerInterface & data_manager)615 const Table *TableManager::GetTable(
616 const mozc::commands::Request &request,
617 const mozc::config::Config &config,
618 const mozc::DataManagerInterface &data_manager) {
619 // calculate the hash depending on the request and the config
620 uint32 hash = request.special_romanji_table();
621 hash = hash * (mozc::config::Config_PreeditMethod_PreeditMethod_MAX + 1)
622 + config.preedit_method();
623 hash =
624 hash * (mozc::config::Config_PunctuationMethod_PunctuationMethod_MAX + 1)
625 + config.punctuation_method();
626 hash = hash * (mozc::config::Config_SymbolMethod_SymbolMethod_MAX + 1)
627 + config.symbol_method();
628
629 // When custom_roman_table is set, force to create new table.
630 bool update_custom_roman_table = false;
631 if ((config.preedit_method() == config::Config::ROMAN) &&
632 config.has_custom_roman_table() &&
633 !config.custom_roman_table().empty()) {
634 const uint32 custom_roman_table_fingerprint =
635 Hash::Fingerprint32(config.custom_roman_table());
636 if (custom_roman_table_fingerprint != custom_roman_table_fingerprint_) {
637 update_custom_roman_table = true;
638 custom_roman_table_fingerprint_ = custom_roman_table_fingerprint;
639 }
640 }
641
642 const auto iterator = table_map_.find(hash);
643 if (iterator != table_map_.end()) {
644 if (update_custom_roman_table) {
645 // Delete the previous table to update the table.
646 table_map_.erase(iterator);
647 } else {
648 return iterator->second.get();
649 }
650 }
651
652 std::unique_ptr<Table> table(new Table());
653 if (!table->InitializeWithRequestAndConfig(request, config, data_manager)) {
654 return nullptr;
655 }
656
657 const Table* ret = table.get();
658 table_map_[hash] = std::move(table);
659 return ret;
660 }
661
ClearCaches()662 void TableManager::ClearCaches() {
663 table_map_.clear();
664 }
665
666 } // namespace composer
667 } // namespace mozc
668