blob: f9d1fc4b3e6f2914726247fdd6b94ef9a95fe347 [file] [log] [blame]
/*
* Copyright (C) 2014 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.tools.lint.checks;
import static com.android.tools.lint.checks.PluralsDatabase.Quantity.few;
import static com.android.tools.lint.checks.PluralsDatabase.Quantity.many;
import static com.android.tools.lint.checks.PluralsDatabase.Quantity.one;
import static com.android.tools.lint.checks.PluralsDatabase.Quantity.two;
import static com.android.tools.lint.checks.PluralsDatabase.Quantity.zero;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.LintUtils;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
/**
* Database used by the {@link com.android.tools.lint.checks.PluralsDetector} to get information
* about plural forms for a given language
*/
public class PluralsDatabase {
private static final EnumSet<Quantity> NONE = EnumSet.noneOf(Quantity.class);
private static final PluralsDatabase sInstance = new PluralsDatabase();
private Map<String, EnumSet<Quantity>> mPlurals = Maps.newHashMap();
/** Bit set if this language uses quantity zero */
@SuppressWarnings("PointlessBitwiseExpression")
static final int FLAG_ZERO = 1 << 0;
/** Bit set if this language uses quantity one */
static final int FLAG_ONE = 1 << 1;
/** Bit set if this language uses quantity two */
static final int FLAG_TWO = 1 << 2;
/** Bit set if this language uses quantity few */
static final int FLAG_FEW = 1 << 3;
/** Bit set if this language uses quantity many */
static final int FLAG_MANY = 1 << 4;
/** Bit set if this language has multiple values that match quantity zero */
static final int FLAG_MULTIPLE_ZERO = 1 << 5;
/** Bit set if this language has multiple values that match quantity one */
static final int FLAG_MULTIPLE_ONE = 1 << 6;
/** Bit set if this language has multiple values that match quantity two */
static final int FLAG_MULTIPLE_TWO = 1 << 7;
@NonNull
public static PluralsDatabase get() {
return sInstance;
}
private static int getFlags(@NonNull String language) {
int index = getLanguageIndex(language);
if (index != -1) {
return FLAGS[index];
}
return 0;
}
private static int getLanguageIndex(@NonNull String language) {
int index = Arrays.binarySearch(LANGUAGE_CODES, language);
if (index >= 0) {
assert LANGUAGE_CODES[index].equals(language);
return index;
} else {
return -1;
}
}
@Nullable
public EnumSet<Quantity> getRelevant(@NonNull String language) {
EnumSet<Quantity> set = mPlurals.get(language);
if (set == null) {
int index = getLanguageIndex(language);
if (index == -1) {
mPlurals.put(language, NONE);
return null;
}
// Process each item and look for relevance
int flag = FLAGS[index];
set = EnumSet.noneOf(Quantity.class);
if ((flag & FLAG_ZERO) != 0) {
set.add(zero);
}
if ((flag & FLAG_ONE) != 0) {
set.add(one);
}
if ((flag & FLAG_TWO) != 0) {
set.add(two);
}
if ((flag & FLAG_FEW) != 0) {
set.add(few);
}
if ((flag & FLAG_MANY) != 0) {
set.add(many);
}
mPlurals.put(language, set);
}
return set == NONE ? null : set;
}
@SuppressWarnings("MethodMayBeStatic")
public boolean hasMultipleValuesForQuantity(
@NonNull String language,
@NonNull Quantity quantity) {
if (quantity == one) {
return (getFlags(language) & FLAG_MULTIPLE_ONE) != 0;
} else if (quantity == two) {
return (getFlags(language) & FLAG_MULTIPLE_TWO) != 0;
} else {
return quantity == zero && (getFlags(language) & FLAG_MULTIPLE_ZERO) != 0;
}
}
@SuppressWarnings("MethodMayBeStatic")
@Nullable
public String findIntegerExamples(@NonNull String language, @NonNull Quantity quantity) {
if (quantity == one) {
return getExampleForQuantityOne(language);
} else if (quantity == two) {
return getExampleForQuantityTwo(language);
} else if (quantity == zero) {
return getExampleForQuantityZero(language);
} else {
return null;
}
}
public enum Quantity {
// deliberately lower case to match attribute names
few, many, one, two, zero, other;
@Nullable
public static Quantity get(@NonNull String name) {
for (Quantity quantity : values()) {
if (name.equals(quantity.name())) {
return quantity;
}
}
return null;
}
public static String formatSet(@NonNull EnumSet<Quantity> set) {
List<String> list = new ArrayList<String>(set.size());
for (Quantity quantity : set) {
list.add('`' + quantity.name() + '`');
}
return LintUtils.formatList(list, Integer.MAX_VALUE);
}
}
// GENERATED DATA.
// This data is generated by the #testDatabaseAccurate method in PluralsDatabaseTest
// which will generate the following if it can find an ICU plurals database file
// in the unit test data folder.
/** Set of language codes relevant to plurals data */
private static final String[] LANGUAGE_CODES = new String[] {
"af", "ak", "am", "ar", "az", "be", "bg", "bh", "bm", "bn",
"bo", "br", "bs", "ca", "cs", "cy", "da", "de", "dv", "dz",
"ee", "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi",
"fo", "fr", "fy", "ga", "gd", "gl", "gu", "gv", "ha", "he",
"hi", "hr", "hu", "hy", "id", "ig", "ii", "in", "is", "it",
"iu", "iw", "ja", "ji", "jv", "ka", "kk", "kl", "km", "kn",
"ko", "ks", "ku", "kw", "ky", "lb", "lg", "ln", "lo", "lt",
"lv", "mg", "mk", "ml", "mn", "mr", "ms", "mt", "my", "nb",
"nd", "ne", "nl", "nn", "no", "nr", "ny", "om", "or", "os",
"pa", "pl", "ps", "pt", "rm", "ro", "ru", "se", "sg", "si",
"sk", "sl", "sn", "so", "sq", "sr", "ss", "st", "sv", "sw",
"ta", "te", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts",
"ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh",
"yi", "yo", "zh", "zu"
};
/**
* Relevant flags for each language (corresponding to each language listed
* in the same position in {@link #LANGUAGE_CODES}).
*/
private static final int[] FLAGS = new int[] {
0x0002, 0x0042, 0x0042, 0x001f, 0x0002, 0x005a, 0x0002, 0x0042,
0x0000, 0x0042, 0x0000, 0x00de, 0x004a, 0x0002, 0x000a, 0x001f,
0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0002, 0x0042, 0x0042, 0x0002, 0x0002, 0x0042,
0x0002, 0x001e, 0x00ce, 0x0002, 0x0042, 0x00ce, 0x0002, 0x0016,
0x0042, 0x004a, 0x0002, 0x0042, 0x0000, 0x0000, 0x0000, 0x0000,
0x0042, 0x0002, 0x0006, 0x0016, 0x0000, 0x0002, 0x0000, 0x0002,
0x0002, 0x0002, 0x0000, 0x0042, 0x0000, 0x0002, 0x0002, 0x0006,
0x0002, 0x0002, 0x0002, 0x0042, 0x0000, 0x004a, 0x0063, 0x0042,
0x0042, 0x0002, 0x0002, 0x0042, 0x0000, 0x001a, 0x0000, 0x0002,
0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0002, 0x0002, 0x0042, 0x001a, 0x0002, 0x0002, 0x0002, 0x000a,
0x005a, 0x0006, 0x0000, 0x0042, 0x000a, 0x00ce, 0x0002, 0x0002,
0x0002, 0x004a, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002,
0x0000, 0x0042, 0x0002, 0x0042, 0x0002, 0x0000, 0x0002, 0x0002,
0x0002, 0x005a, 0x0002, 0x0002, 0x0002, 0x0000, 0x0002, 0x0042,
0x0000, 0x0002, 0x0002, 0x0000, 0x0000, 0x0042
};
@Nullable
private static String getExampleForQuantityZero(@NonNull String language) {
int index = getLanguageIndex(language);
switch (index) {
// set14
case 70: // lv
return "0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, \u2026";
case -1:
default:
return null;
}
}
@Nullable
private static String getExampleForQuantityOne(@NonNull String language) {
int index = getLanguageIndex(language);
switch (index) {
// set1
case 2: // am
case 9: // bn
case 27: // fa
case 36: // gu
case 40: // hi
case 59: // kn
case 75: // mr
case 133: // zu
return "0, 1";
// set11
case 48: // is
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set12
case 72: // mk
return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
// set13
case 115: // tl
return "0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, \u2026";
// set14
case 70: // lv
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set2
case 28: // ff
case 31: // fr
case 43: // hy
return "0, 1";
// set20
case 12: // bs
case 41: // hr
case 105: // sr
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set21
case 34: // gd
return "1, 11";
// set22
case 101: // sl
return "1, 101, 201, 301, 401, 501, 601, 701, 1001, \u2026";
// set26
case 5: // be
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set27
case 69: // lt
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set29
case 96: // ru
case 121: // uk
return "1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, \u2026";
// set30
case 11: // br
return "1, 21, 31, 41, 51, 61, 81, 101, 1001, \u2026";
// set32
case 37: // gv
return "1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, \u2026";
// set5
case 99: // si
return "0, 1";
// set6
case 1: // ak
case 7: // bh
case 67: // ln
case 71: // mg
case 90: // pa
case 113: // ti
case 127: // wa
return "0, 1";
case -1:
default:
return null;
}
}
@Nullable
private static String getExampleForQuantityTwo(@NonNull String language) {
int index = getLanguageIndex(language);
switch (index) {
// set21
case 34: // gd
return "2, 12";
// set22
case 101: // sl
return "2, 102, 202, 302, 402, 502, 602, 702, 1002, \u2026";
// set30
case 11: // br
return "2, 22, 32, 42, 52, 62, 82, 102, 1002, \u2026";
// set32
case 37: // gv
return "2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, \u2026";
case -1:
default:
return null;
}
}
}