1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.latin;
18 
19 import android.text.TextUtils;
20 
21 import com.android.inputmethod.latin.utils.StringUtils;
22 
23 import java.util.Arrays;
24 
25 /**
26  * Class to represent information of previous words. This class is used to add n-gram entries
27  * into binary dictionaries, to get predictions, and to get suggestions.
28  */
29 public class PrevWordsInfo {
30     public static final PrevWordsInfo EMPTY_PREV_WORDS_INFO =
31             new PrevWordsInfo(WordInfo.EMPTY_WORD_INFO);
32     public static final PrevWordsInfo BEGINNING_OF_SENTENCE =
33             new PrevWordsInfo(WordInfo.BEGINNING_OF_SENTENCE);
34 
35     /**
36      * Word information used to represent previous words information.
37      */
38     public static class WordInfo {
39         public static final WordInfo EMPTY_WORD_INFO = new WordInfo(null);
40         public static final WordInfo BEGINNING_OF_SENTENCE = new WordInfo();
41 
42         // This is an empty char sequence when mIsBeginningOfSentence is true.
43         public final CharSequence mWord;
44         // TODO: Have sentence separator.
45         // Whether the current context is beginning of sentence or not. This is true when composing
46         // at the beginning of an input field or composing a word after a sentence separator.
47         public final boolean mIsBeginningOfSentence;
48 
49         // Beginning of sentence.
WordInfo()50         public WordInfo() {
51             mWord = "";
52             mIsBeginningOfSentence = true;
53         }
54 
WordInfo(final CharSequence word)55         public WordInfo(final CharSequence word) {
56             mWord = word;
57             mIsBeginningOfSentence = false;
58         }
59 
isValid()60         public boolean isValid() {
61             return mWord != null;
62         }
63 
64         @Override
hashCode()65         public int hashCode() {
66             return Arrays.hashCode(new Object[] { mWord, mIsBeginningOfSentence } );
67         }
68 
69         @Override
equals(Object o)70         public boolean equals(Object o) {
71             if (this == o) return true;
72             if (!(o instanceof WordInfo)) return false;
73             final WordInfo wordInfo = (WordInfo)o;
74             if (mWord == null || wordInfo.mWord == null) {
75                 return mWord == wordInfo.mWord
76                         && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
77             }
78             return TextUtils.equals(mWord, wordInfo.mWord)
79                     && mIsBeginningOfSentence == wordInfo.mIsBeginningOfSentence;
80         }
81     }
82 
83     // The words immediately before the considered word. EMPTY_WORD_INFO element means we don't
84     // have any context for that previous word including the "beginning of sentence context" - we
85     // just don't know what to predict using the information. An example of that is after a comma.
86     // For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the
87     // WordComposer was reset and before starting a new composing word, but we should never be
88     // calling getSuggetions* in this situation.
89     public WordInfo[] mPrevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
90 
91     // Construct from the previous word information.
PrevWordsInfo(final WordInfo prevWordInfo)92     public PrevWordsInfo(final WordInfo prevWordInfo) {
93         mPrevWordsInfo[0] = prevWordInfo;
94     }
95 
96     // Construct from WordInfo array. n-th element represents (n+1)-th previous word's information.
PrevWordsInfo(final WordInfo[] prevWordsInfo)97     public PrevWordsInfo(final WordInfo[] prevWordsInfo) {
98         for (int i = 0; i < Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM; i++) {
99             mPrevWordsInfo[i] =
100                     (prevWordsInfo.length > i) ? prevWordsInfo[i] : WordInfo.EMPTY_WORD_INFO;
101         }
102     }
103 
104     // Create next prevWordsInfo using current prevWordsInfo.
getNextPrevWordsInfo(final WordInfo wordInfo)105     public PrevWordsInfo getNextPrevWordsInfo(final WordInfo wordInfo) {
106         final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
107         prevWordsInfo[0] = wordInfo;
108         for (int i = 1; i < prevWordsInfo.length; i++) {
109             prevWordsInfo[i] = mPrevWordsInfo[i - 1];
110         }
111         return new PrevWordsInfo(prevWordsInfo);
112     }
113 
isValid()114     public boolean isValid() {
115         return mPrevWordsInfo[0].isValid();
116     }
117 
outputToArray(final int[][] codePointArrays, final boolean[] isBeginningOfSentenceArray)118     public void outputToArray(final int[][] codePointArrays,
119             final boolean[] isBeginningOfSentenceArray) {
120         for (int i = 0; i < mPrevWordsInfo.length; i++) {
121             final WordInfo wordInfo = mPrevWordsInfo[i];
122             if (wordInfo == null || !wordInfo.isValid()) {
123                 codePointArrays[i] = new int[0];
124                 isBeginningOfSentenceArray[i] = false;
125                 continue;
126             }
127             codePointArrays[i] = StringUtils.toCodePointArray(wordInfo.mWord);
128             isBeginningOfSentenceArray[i] = wordInfo.mIsBeginningOfSentence;
129         }
130     }
131 
132     @Override
hashCode()133     public int hashCode() {
134         return Arrays.hashCode(mPrevWordsInfo);
135     }
136 
137     @Override
equals(Object o)138     public boolean equals(Object o) {
139         if (this == o) return true;
140         if (!(o instanceof PrevWordsInfo)) return false;
141         final PrevWordsInfo prevWordsInfo = (PrevWordsInfo)o;
142         return Arrays.equals(mPrevWordsInfo, prevWordsInfo.mPrevWordsInfo);
143     }
144 
145     @Override
toString()146     public String toString() {
147         final StringBuffer builder = new StringBuffer();
148         for (int i = 0; i < mPrevWordsInfo.length; i++) {
149             final WordInfo wordInfo = mPrevWordsInfo[i];
150             builder.append("PrevWord[");
151             builder.append(i);
152             builder.append("]: ");
153             if (wordInfo == null || !wordInfo.isValid()) {
154                 builder.append("Empty. ");
155                 continue;
156             }
157             builder.append(wordInfo.mWord);
158             builder.append(", isBeginningOfSentence: ");
159             builder.append(wordInfo.mIsBeginningOfSentence);
160             builder.append(". ");
161         }
162         return builder.toString();
163     }
164 }
165