/* * 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; } } }