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