15f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi/*
25f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * Copyright (C) 2011 The Android Open Source Project
35f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi *
45f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * Licensed under the Apache License, Version 2.0 (the "License");
55f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * you may not use this file except in compliance with the License.
65f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * You may obtain a copy of the License at
75f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi *
85f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi *      http://www.apache.org/licenses/LICENSE-2.0
95f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi *
105f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * Unless required by applicable law or agreed to in writing, software
115f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * distributed under the License is distributed on an "AS IS" BASIS,
125f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * See the License for the specific language governing permissions and
145f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * limitations under the License.
155f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi */
165f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
175f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagipackage com.android.inputmethod.latin.makedict;
185f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
195f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagiimport com.android.inputmethod.annotations.UsedForTesting;
205f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagiimport com.android.inputmethod.latin.BinaryDictionary;
215f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaokaimport com.android.inputmethod.latin.Dictionary;
22c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagiimport com.android.inputmethod.latin.NgramContext;
23c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagiimport com.android.inputmethod.latin.NgramContext.WordInfo;
244beeb9253a06482299e0c67467531d30436a02fcJean Chalardimport com.android.inputmethod.latin.common.StringUtils;
25b24de426fc98b7550406f54075de4bbbdb2e0ee2Keisuke Kuroyanagiimport com.android.inputmethod.latin.utils.CombinedFormatUtils;
265f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
275f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagiimport java.util.ArrayList;
285f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagiimport java.util.Arrays;
295f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
30d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagiimport javax.annotation.Nullable;
31d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi
325f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi/**
335f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * Utility class for a word with a probability.
345f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi *
355f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi * This is chiefly used to iterate a dictionary.
365f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi */
375f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagipublic final class WordProperty implements Comparable<WordProperty> {
385f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public final String mWord;
395f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public final ProbabilityInfo mProbabilityInfo;
40c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi    public final ArrayList<NgramProperty> mNgrams;
411adca93381d261a6070be2721dbf8b8abafbfe01Keisuke Kuroyanagi    // TODO: Support mIsBeginningOfSentence.
421adca93381d261a6070be2721dbf8b8abafbfe01Keisuke Kuroyanagi    public final boolean mIsBeginningOfSentence;
435f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public final boolean mIsNotAWord;
4405172bf1a5693c2e108e91436b98ecd35d2dadadAdrian Velicu    public final boolean mIsPossiblyOffensive;
45c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi    public final boolean mHasNgrams;
465f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
475f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    private int mHashCode = 0;
485f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
49c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi    // TODO: Support n-gram.
50aa7abb2d89e559d3e8969b35fe2c75a8793495f1Keisuke Kuroyanagi    @UsedForTesting
518ffc631826b108423f98e3ff4d987f067cbc4e0cKeisuke Kuroyanagi    public WordProperty(final String word, final ProbabilityInfo probabilityInfo,
52d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi            @Nullable final ArrayList<WeightedString> bigrams,
5305172bf1a5693c2e108e91436b98ecd35d2dadadAdrian Velicu            final boolean isNotAWord, final boolean isPossiblyOffensive) {
545f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        mWord = word;
558ffc631826b108423f98e3ff4d987f067cbc4e0cKeisuke Kuroyanagi        mProbabilityInfo = probabilityInfo;
56b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        if (null == bigrams) {
57b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard            mNgrams = null;
58b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        } else {
59b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard            mNgrams = new ArrayList<>();
60b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard            final NgramContext ngramContext = new NgramContext(new WordInfo(mWord));
615f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka            for (final WeightedString bigramTarget : bigrams) {
625f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka                mNgrams.add(new NgramProperty(bigramTarget, ngramContext));
63c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi            }
64c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        }
651adca93381d261a6070be2721dbf8b8abafbfe01Keisuke Kuroyanagi        mIsBeginningOfSentence = false;
665f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        mIsNotAWord = isNotAWord;
6705172bf1a5693c2e108e91436b98ecd35d2dadadAdrian Velicu        mIsPossiblyOffensive = isPossiblyOffensive;
68c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        mHasNgrams = bigrams != null && !bigrams.isEmpty();
695f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
705f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
715f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    private static ProbabilityInfo createProbabilityInfoFromArray(final int[] probabilityInfo) {
725f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi      return new ProbabilityInfo(
735f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi              probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_PROBABILITY_INDEX],
745f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi              probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX],
755f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi              probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_LEVEL_INDEX],
765f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi              probabilityInfo[BinaryDictionary.FORMAT_WORD_PROPERTY_COUNT_INDEX]);
775f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
785f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
795f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    // Construct word property using information from native code.
805f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    // This represents invalid word when the probability is BinaryDictionary.NOT_A_PROBABILITY.
815f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public WordProperty(final int[] codePoints, final boolean isNotAWord,
8212d80ebead6a1d7f704a5a3af3b6fe3313ceab05Dan Zivkovic            final boolean isPossiblyOffensive, final boolean hasBigram,
8388fa47a27d45f6460971d0d223aa558e121b3478Keisuke Kuroyanagi            final boolean isBeginningOfSentence, final int[] probabilityInfo,
84d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi            final ArrayList<int[][]> ngramPrevWordsArray,
85b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            final ArrayList<boolean[]> ngramPrevWordIsBeginningOfSentenceArray,
8612d80ebead6a1d7f704a5a3af3b6fe3313ceab05Dan Zivkovic            final ArrayList<int[]> ngramTargets, final ArrayList<int[]> ngramProbabilityInfo) {
875f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
885f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
89b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        final ArrayList<NgramProperty> ngrams = new ArrayList<>();
9088fa47a27d45f6460971d0d223aa558e121b3478Keisuke Kuroyanagi        mIsBeginningOfSentence = isBeginningOfSentence;
915f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        mIsNotAWord = isNotAWord;
9205172bf1a5693c2e108e91436b98ecd35d2dadadAdrian Velicu        mIsPossiblyOffensive = isPossiblyOffensive;
93c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        mHasNgrams = hasBigram;
94c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi
95d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi        final int relatedNgramCount = ngramTargets.size();
96c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        for (int i = 0; i < relatedNgramCount; i++) {
97c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi            final String ngramTargetString =
98d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi                    StringUtils.getStringFromNullTerminatedCodePointArray(ngramTargets.get(i));
99c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi            final WeightedString ngramTarget = new WeightedString(ngramTargetString,
100d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi                    createProbabilityInfoFromArray(ngramProbabilityInfo.get(i)));
101b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            final int[][] prevWords = ngramPrevWordsArray.get(i);
102b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            final boolean[] isBeginningOfSentenceArray =
103b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi                    ngramPrevWordIsBeginningOfSentenceArray.get(i);
104b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            final WordInfo[] wordInfoArray = new WordInfo[prevWords.length];
105b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            for (int j = 0; j < prevWords.length; j++) {
106b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi                wordInfoArray[j] = isBeginningOfSentenceArray[j]
107b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi                        ? WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO
108b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi                        : new WordInfo(StringUtils.getStringFromNullTerminatedCodePointArray(
109b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi                                prevWords[j]));
110b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            }
111b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi            final NgramContext ngramContext = new NgramContext(wordInfoArray);
112b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard            ngrams.add(new NgramProperty(ngramTarget, ngramContext));
1135f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        }
114b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        mNgrams = ngrams.isEmpty() ? null : ngrams;
1155f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1165f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
117c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi    // TODO: Remove
118b5ef884fbb6bfd08ce793604cdf7f0941e958a84Keisuke Kuroyanagi    @UsedForTesting
119c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi    public ArrayList<WeightedString> getBigrams() {
120b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        if (null == mNgrams) {
121b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard            return null;
122b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        }
123c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        final ArrayList<WeightedString> bigrams = new ArrayList<>();
124c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        for (final NgramProperty ngram : mNgrams) {
125c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi            if (ngram.mNgramContext.getPrevWordCount() == 1) {
126c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi                bigrams.add(ngram.mTargetWord);
127c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi            }
128c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        }
129c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi        return bigrams;
130c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi    }
131c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi
1325f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public int getProbability() {
1335f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        return mProbabilityInfo.mProbability;
1345f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1355f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
1365f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    private static int computeHashCode(WordProperty word) {
1375f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        return Arrays.hashCode(new Object[] {
1385f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi                word.mWord,
1395f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi                word.mProbabilityInfo,
140c6a6f6a9905ab98516d944ac85933d016e4147fbKeisuke Kuroyanagi                word.mNgrams,
1415f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi                word.mIsNotAWord,
14205172bf1a5693c2e108e91436b98ecd35d2dadadAdrian Velicu                word.mIsPossiblyOffensive
1435f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        });
1445f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1455f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
1465f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    /**
1475f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     * Three-way comparison.
1485f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     *
1495f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     * A Word x is greater than a word y if x has a higher frequency. If they have the same
1505f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     * frequency, they are sorted in lexicographic order.
1515f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     */
1525f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    @Override
1535f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public int compareTo(final WordProperty w) {
1545f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        if (getProbability() < w.getProbability()) return 1;
1555f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        if (getProbability() > w.getProbability()) return -1;
1565f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        return mWord.compareTo(w.mWord);
1575f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1585f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
1595f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    /**
1605f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     * Equality test.
1615f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     *
1625f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     * Words are equal if they have the same frequency, the same spellings, and the same
1635f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     * attributes.
1645f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi     */
1655f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    @Override
1665f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public boolean equals(Object o) {
1675f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        if (o == this) return true;
1685f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        if (!(o instanceof WordProperty)) return false;
1695f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        WordProperty w = (WordProperty)o;
17012d80ebead6a1d7f704a5a3af3b6fe3313ceab05Dan Zivkovic        return mProbabilityInfo.equals(w.mProbabilityInfo)
17112d80ebead6a1d7f704a5a3af3b6fe3313ceab05Dan Zivkovic                && mWord.equals(w.mWord) && equals(mNgrams, w.mNgrams)
17205172bf1a5693c2e108e91436b98ecd35d2dadadAdrian Velicu                && mIsNotAWord == w.mIsNotAWord && mIsPossiblyOffensive == w.mIsPossiblyOffensive
17312d80ebead6a1d7f704a5a3af3b6fe3313ceab05Dan Zivkovic                && mHasNgrams == w.mHasNgrams;
1745f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1755f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
176d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi    // TDOO: Have a utility method like java.util.Objects.equals.
177d7a51c242bd21aed28b33279add1a2d54cf3bd60Keisuke Kuroyanagi    private static <T> boolean equals(final ArrayList<T> a, final ArrayList<T> b) {
178b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        if (null == a) {
179b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard            return null == b;
180b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        }
181b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard        return a.equals(b);
182b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard    }
183b28d1cc48794019e63b95b197a6b9cd3fe64275cJean Chalard
1845f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    @Override
1855f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public int hashCode() {
1865f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        if (mHashCode == 0) {
1875f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi            mHashCode = computeHashCode(this);
1885f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        }
1895f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi        return mHashCode;
1905f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1915f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
1925f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    @UsedForTesting
1935f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public boolean isValid() {
1945f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka        return getProbability() != Dictionary.NOT_A_PROBABILITY;
1955f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
1965f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi
1975f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    @Override
1985f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    public String toString() {
199b24de426fc98b7550406f54075de4bbbdb2e0ee2Keisuke Kuroyanagi        return CombinedFormatUtils.formatWordProperty(this);
2005f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi    }
2015f5feeba13f6f1a907d90365d8037a361d0ff5daKeisuke Kuroyanagi}
202