1666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada/* 2666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Copyright (C) 2012 The Android Open Source Project 3666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * 4666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * use this file except in compliance with the License. You may obtain a copy of 6666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * the License at 7666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * 8666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * http://www.apache.org/licenses/LICENSE-2.0 9666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * 10666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Unless required by applicable law or agreed to in writing, software 11666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * License for the specific language governing permissions and limitations under 14666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * the License. 15666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada */ 16666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 17666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadapackage com.android.inputmethod.latin; 18666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 19666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport android.util.Log; 20666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 2165feee12e5889601e375d92dfdf5f8e8fbb05092Yuichiro Hanadaimport com.android.inputmethod.latin.makedict.BinaryDictIOUtils; 22666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport com.android.inputmethod.latin.makedict.BinaryDictInputOutput; 23666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; 241a347723c5ad4a71076df67f3af3b702db205719Yuichiro Hanadaimport com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; 25666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport com.android.inputmethod.latin.makedict.FusionDictionary; 26666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport com.android.inputmethod.latin.makedict.FusionDictionary.Node; 27666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport com.android.inputmethod.latin.makedict.PendingAttribute; 28666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport com.android.inputmethod.latin.makedict.UnsupportedFormatException; 29666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 30666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport java.io.IOException; 31666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport java.io.OutputStream; 32666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport java.util.ArrayList; 33666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport java.util.HashMap; 34666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanadaimport java.util.Map; 35666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 36666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada/** 37666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Reads and writes Binary files for a UserHistoryDictionary. 38666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * 39666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * All the methods in this class are static. 40666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada */ 41a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class UserHistoryDictIOUtils { 42666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName(); 43666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada private static final boolean DEBUG = false; 44666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 45666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public interface OnAddWordListener { 46666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public void setUnigram(final String word, final String shortcutTarget, final int frequency); 47666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public void setBigram(final String word1, final String word2, final int frequency); 48666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 49666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 50666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public interface BigramDictionaryInterface { 51666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public int getFrequency(final String word1, final String word2); 52666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 53666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 54666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public static final class ByteArrayWrapper implements FusionDictionaryBufferInterface { 55666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada private byte[] mBuffer; 56666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada private int mPosition; 57666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 58be5db53a09a705575e3902769b44d687142f6a83Yuichiro Hanada public ByteArrayWrapper(final byte[] buffer) { 59666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada mBuffer = buffer; 60666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada mPosition = 0; 61666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 62666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 63666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada @Override 64666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public int readUnsignedByte() { 65666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada return ((int)mBuffer[mPosition++]) & 0xFF; 66666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 67666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 68666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada @Override 69666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public int readUnsignedShort() { 70666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final int retval = readUnsignedByte(); 71666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada return (retval << 8) + readUnsignedByte(); 72666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 73666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 74666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada @Override 75666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public int readUnsignedInt24() { 76666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final int retval = readUnsignedShort(); 77666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada return (retval << 8) + readUnsignedByte(); 78666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 79666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 80666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada @Override 81666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public int readInt() { 82666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final int retval = readUnsignedShort(); 83666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada return (retval << 16) + readUnsignedShort(); 84666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 85666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 86666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada @Override 87666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public int position() { 88666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada return mPosition; 89666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 90666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 91666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada @Override 92666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public void position(int position) { 93666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada mPosition = position; 94666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 958d031a63b4d59d4d8670b4310dd9e18a0e03435aYuichiro Hanada 968d031a63b4d59d4d8670b4310dd9e18a0e03435aYuichiro Hanada @Override 978d031a63b4d59d4d8670b4310dd9e18a0e03435aYuichiro Hanada public void put(final byte b) { 988d031a63b4d59d4d8670b4310dd9e18a0e03435aYuichiro Hanada mBuffer[mPosition++] = b; 998d031a63b4d59d4d8670b4310dd9e18a0e03435aYuichiro Hanada } 100a149c53c8ebe1b2acb7ee92eac51ccdc364162e7Yuichiro Hanada 101a149c53c8ebe1b2acb7ee92eac51ccdc364162e7Yuichiro Hanada @Override 102a149c53c8ebe1b2acb7ee92eac51ccdc364162e7Yuichiro Hanada public int limit() { 103a161bdac885fc8e5f0063d33b055b0a6ecdefbdbYuichiro Hanada return mBuffer.length - 1; 104a161bdac885fc8e5f0063d33b055b0a6ecdefbdbYuichiro Hanada } 105a161bdac885fc8e5f0063d33b055b0a6ecdefbdbYuichiro Hanada 106a161bdac885fc8e5f0063d33b055b0a6ecdefbdbYuichiro Hanada @Override 107a161bdac885fc8e5f0063d33b055b0a6ecdefbdbYuichiro Hanada public int capacity() { 108a149c53c8ebe1b2acb7ee92eac51ccdc364162e7Yuichiro Hanada return mBuffer.length; 109a149c53c8ebe1b2acb7ee92eac51ccdc364162e7Yuichiro Hanada } 110666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 111666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 112666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada /** 113666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Writes dictionary to file. 114666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada */ 115666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public static void writeDictionaryBinary(final OutputStream destination, 116666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams, 11783dfe0fd8c7e2bce2717930dbf8732f5414ee39dYuichiro Hanada final FormatOptions formatOptions) { 118666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams); 119666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada try { 12083dfe0fd8c7e2bce2717930dbf8732f5414ee39dYuichiro Hanada BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions); 12184d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada Log.d(TAG, "end writing"); 122666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } catch (IOException e) { 123666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada Log.e(TAG, "IO exception while writing file: " + e); 124666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } catch (UnsupportedFormatException e) { 125666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada Log.e(TAG, "Unsupported fomat: " + e); 126666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 127666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 128666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 129666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada /** 130666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Constructs a new FusionDictionary from BigramDictionaryInterface. 131666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada */ 132666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada /* packages for test */ static FusionDictionary constructFusionDictionary( 133666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) { 134666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final FusionDictionary fusionDict = new FusionDictionary(new Node(), 13584d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, 13684d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada false)); 13784d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada int profTotal = 0; 138666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada for (final String word1 : bigrams.keySet()) { 139666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1); 140666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada for (final String word2 : word1Bigrams.keySet()) { 141666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final int freq = dict.getFrequency(word1, word2); 14284d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada if (freq == -1) { 14384d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada // don't add this bigram. 14484d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada continue; 14584d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada } 146666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada if (DEBUG) { 147666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada if (word1 == null) { 148666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq)); 149666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } else { 150666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada Log.d(TAG, "add bigram: " + word1 151666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada + "," + word2 + "," + Integer.toString(freq)); 152666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 15384d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada profTotal++; 154666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 155666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada if (word1 == null) { // unigram 1568251036616dc30db9ad1a981cc5af5843d474647Jean Chalard fusionDict.add(word2, freq, null, false /* isNotAWord */); 157666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } else { // bigram 15884d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada if (FusionDictionary.findWordInTree(fusionDict.mRoot, word1) == null) { 15984d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada fusionDict.add(word1, 2, null, false /* isNotAWord */); 16084d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada } 161666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada fusionDict.setBigram(word1, word2, freq); 162666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 163666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada bigrams.updateBigram(word1, word2, (byte)freq); 164666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 165666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 16684d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada if (DEBUG) { 16784d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada Log.d(TAG, "add " + profTotal + "words"); 16884d858ed5e187eb9d4b56b593e1d9287f762bbcaYuichiro Hanada } 169666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada return fusionDict; 170666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 171666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 172666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada /** 173666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Reads dictionary from file. 174666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada */ 175666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada public static void readDictionaryBinary(final FusionDictionaryBufferInterface buffer, 176666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final OnAddWordListener dict) { 177666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final Map<Integer, String> unigrams = CollectionUtils.newTreeMap(); 178666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap(); 179666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap(); 180666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada try { 18165feee12e5889601e375d92dfdf5f8e8fbb05092Yuichiro Hanada BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies, 182666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada bigrams); 183666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } catch (IOException e) { 184666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada Log.e(TAG, "IO exception while reading file: " + e); 185666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } catch (UnsupportedFormatException e) { 186666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada Log.e(TAG, "Unsupported format: " + e); 1877a7d117aa7e7157ed429d68c4797218476151d8dYuichiro Hanada } catch (ArrayIndexOutOfBoundsException e) { 1887a7d117aa7e7157ed429d68c4797218476151d8dYuichiro Hanada Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file: " + e); 189666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 1907a7d117aa7e7157ed429d68c4797218476151d8dYuichiro Hanada addWordsFromWordMap(unigrams, frequencies, bigrams, dict); 191666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 192666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 193666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada /** 194666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada * Adds all unigrams and bigrams in maps to OnAddWordListener. 195666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada */ 196666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams, 197666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final Map<Integer, Integer> frequencies, 198666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) { 199666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada for (Map.Entry<Integer, String> entry : unigrams.entrySet()) { 200666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final String word1 = entry.getValue(); 201666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final int unigramFrequency = frequencies.get(entry.getKey()); 202666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada to.setUnigram(word1, null, unigramFrequency); 203666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey()); 204666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada if (attrList != null) { 205666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada for (final PendingAttribute attr : attrList) { 206666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada to.setBigram(word1, unigrams.get(attr.mAddress), 207666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency, 208666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada attr.mFrequency)); 209666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 210666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 211666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 212666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada 213666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada } 214666a4338026866df1f18dd6b3f968c3788943e4cYuichiro Hanada}