aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/DictionaryFactory.java
blob: 7a59d80f1a2db0f796e107ce32e47fe2b0269d0f (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.inputmethod.latin;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.util.Log;

import com.android.inputmethod.latin.LocaleUtils.RunInLocale;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Locale;

/**
 * Factory for dictionary instances.
 */
public class DictionaryFactory {
    private static String TAG = DictionaryFactory.class.getSimpleName();

    /**
     * Initializes a dictionary from a dictionary pack, with explicit flags.
     *
     * This searches for a content provider providing a dictionary pack for the specified
     * locale. If none is found, it falls back to using the resource passed as fallBackResId
     * as a dictionary.
     * @param context application context for reading resources
     * @param locale the locale for which to create the dictionary
     * @param fallbackResId the id of the resource to use as a fallback if no pack is found
     * @param flagArray an array of flags to use
     * @return an initialized instance of DictionaryCollection
     */
    public static DictionaryCollection createDictionaryFromManager(final Context context,
            final Locale locale, final int fallbackResId, final Flag[] flagArray) {
        if (null == locale) {
            Log.e(TAG, "No locale defined for dictionary");
            return new DictionaryCollection(createBinaryDictionary(context, fallbackResId, locale));
        }

        final LinkedList<Dictionary> dictList = new LinkedList<Dictionary>();
        final ArrayList<AssetFileAddress> assetFileList =
                BinaryDictionaryGetter.getDictionaryFiles(locale, context, fallbackResId);
        if (null != assetFileList) {
            for (final AssetFileAddress f : assetFileList) {
                final BinaryDictionary binaryDictionary =
                        new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, flagArray,
                                locale);
                if (binaryDictionary.isValidDictionary()) {
                    dictList.add(binaryDictionary);
                }
            }
        }

        // If the list is empty, that means we should not use any dictionary (for example, the user
        // explicitly disabled the main dictionary), so the following is okay. dictList is never
        // null, but if for some reason it is, DictionaryCollection handles it gracefully.
        return new DictionaryCollection(dictList);
    }

    /**
     * Initializes a dictionary from a dictionary pack, with default flags.
     *
     * This searches for a content provider providing a dictionary pack for the specified
     * locale. If none is found, it falls back to using the resource passed as fallBackResId
     * as a dictionary.
     * @param context application context for reading resources
     * @param locale the locale for which to create the dictionary
     * @param fallbackResId the id of the resource to use as a fallback if no pack is found
     * @return an initialized instance of DictionaryCollection
     */
    public static DictionaryCollection createDictionaryFromManager(final Context context,
            final Locale locale, final int fallbackResId) {
        return createDictionaryFromManager(context, locale, fallbackResId, null);
    }

    /**
     * Initializes a dictionary from a raw resource file
     * @param context application context for reading resources
     * @param resId the resource containing the raw binary dictionary
     * @param locale the locale to use for the resource
     * @return an initialized instance of BinaryDictionary
     */
    protected static BinaryDictionary createBinaryDictionary(final Context context,
            final int resId, final Locale locale) {
        AssetFileDescriptor afd = null;
        try {
            final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
                @Override
                protected AssetFileDescriptor job(Resources res) {
                    return res.openRawResourceFd(resId);
                }
            };
            afd = job.runInLocale(context.getResources(), locale);
            if (afd == null) {
                Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
                return null;
            }
            if (!isFullDictionary(afd)) return null;
            final String sourceDir = context.getApplicationInfo().sourceDir;
            final File packagePath = new File(sourceDir);
            // TODO: Come up with a way to handle a directory.
            if (!packagePath.isFile()) {
                Log.e(TAG, "sourceDir is not a file: " + sourceDir);
                return null;
            }
            return new BinaryDictionary(context,
                    sourceDir, afd.getStartOffset(), afd.getLength(), null, locale);
        } catch (android.content.res.Resources.NotFoundException e) {
            Log.e(TAG, "Could not find the resource. resId=" + resId);
            return null;
        } finally {
            if (null != afd) {
                try {
                    afd.close();
                } catch (java.io.IOException e) {
                    /* IOException on close ? What am I supposed to do ? */
                }
            }
        }
    }

    /**
     * Create a dictionary from passed data. This is intended for unit tests only.
     * @param context the test context to create this data from.
     * @param dictionary the file to read
     * @param startOffset the offset in the file where the data starts
     * @param length the length of the data
     * @param flagArray the flags to use with this data for testing
     * @return the created dictionary, or null.
     */
    public static Dictionary createDictionaryForTest(Context context, File dictionary,
            long startOffset, long length, Flag[] flagArray) {
        if (dictionary.isFile()) {
            return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
                    flagArray, null);
        } else {
            Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
            return null;
        }
    }

    /**
     * Find out whether a dictionary is available for this locale.
     * @param context the context on which to check resources.
     * @param locale the locale to check for.
     * @return whether a (non-placeholder) dictionary is available or not.
     */
    public static boolean isDictionaryAvailable(Context context, Locale locale) {
        final RunInLocale<Boolean> job = new RunInLocale<Boolean>() {
            @Override
            protected Boolean job(Resources res) {
                final int resourceId = getMainDictionaryResourceId(res);
                final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
                final boolean hasDictionary = isFullDictionary(afd);
                try {
                    if (null != afd) afd.close();
                } catch (java.io.IOException e) {
                    /* Um, what can we do here exactly? */
                }
                return hasDictionary;
            }
        };
        return job.runInLocale(context.getResources(), locale);
    }

    // TODO: Do not use the size of the dictionary as an unique dictionary ID.
    public static Long getDictionaryId(final Context context, final Locale locale) {
        final RunInLocale<Long> job = new RunInLocale<Long>() {
            @Override
            protected Long job(Resources res) {
                final int resourceId = getMainDictionaryResourceId(res);
                final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
                final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
                        ? afd.getLength()
                        : null;
                try {
                    if (null != afd) afd.close();
                } catch (java.io.IOException e) {
                }
                return size;
            }
        };
        return job.runInLocale(context.getResources(), locale);
    }

    // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
    // Suggestion : strip the locale, open the placeholder file and store its offset.
    // Upon opening the file, if it's the same offset, then it's the placeholder.
    private static final long PLACEHOLDER_LENGTH = 34;
    /**
     * Finds out whether the data pointed out by an AssetFileDescriptor is a full
     * dictionary (as opposed to null, or to a place holder).
     * @param afd the file descriptor to test, or null
     * @return true if the dictionary is a real full dictionary, false if it's null or a placeholder
     */
    protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
        return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
    }

    /**
     * Returns a main dictionary resource id
     * @return main dictionary resource id
     */
    public static int getMainDictionaryResourceId(Resources res) {
        final String MAIN_DIC_NAME = "main";
        String packageName = LatinIME.class.getPackage().getName();
        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
    }
}