/*
 * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302
 * @summary Verify implementation for Locale matching.
 * @run testng/othervm LocaleMatchingTest
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Locale.FilteringMode;
import java.util.Locale.LanguageRange;
import java.util.Map;

import static java.util.Locale.FilteringMode.*;
import static java.util.Locale.LanguageRange.*;
import static org.testng.Assert.*;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class LocaleMatchingTest {

    @DataProvider(name = "LRConstructorData")
    Object[][] LRConstructorData() {
        return new Object[][] {
                // Range, Weight
                {"elvish", MAX_WEIGHT},
                {"de-DE", MAX_WEIGHT},
                {"de-Latn-DE-1996", MAX_WEIGHT},
                {"zh-Hant-CN-x-private1-private2", MAX_WEIGHT},
                {"ar", 0.8},
                {"en-US", 0.5},
                {"sr-Latn-BA", 0},
                {"ja", 1},
        };
    }

    @DataProvider(name = "LRConstructorNPEData")
    Object[][] LRConstructorNPEData() {
        return new Object[][] {
                // Range, Weight
                {null, MAX_WEIGHT},
                {null, 0.8},
        };
    }

    @DataProvider(name = "LRConstructorIAEData")
    Object[][] LRConstructorIAEData() {
        return new Object[][] {
                // Range, Weight
                {"ja", -0.8},
                {"Elvish", 3.0},
                {"-ja", MAX_WEIGHT},
                {"ja--JP", MAX_WEIGHT},
                {"en-US-", MAX_WEIGHT},
                {"a4r", MAX_WEIGHT},
                {"ar*", MAX_WEIGHT},
                {"ar-*EG", MAX_WEIGHT},
                {"abcdefghijklmn", MAX_WEIGHT},
                {"ja-J=", MAX_WEIGHT},
                {"ja-opqrstuvwxyz", MAX_WEIGHT},
                {"zh_CN", MAX_WEIGHT},
                {"1996-de-Latn", MAX_WEIGHT},
                // Testcase for 8042360
                {"en-Latn-1234567890", MAX_WEIGHT},
        };
    }

    @DataProvider(name = "LRParseData")
    Object[][] LRParseData() {
        return new Object[][] {
                // Ranges, Expected result
                {"Accept-Language: fr-FX, de-DE;q=0.5, fr-tp-x-FOO;q=0.1, "
                        + "en-X-tp;q=0.6, en-FR;q=0.7, de-de;q=0.8, iw;q=0.4, "
                        + "he;q=0.4, de-de;q=0.5, ja, in-tpp, in-tp;q=0.2",
                        List.of(new LanguageRange("fr-fx", 1.0),
                                new LanguageRange("fr-fr", 1.0),
                                new LanguageRange("ja", 1.0),
                                new LanguageRange("in-tpp", 1.0),
                                new LanguageRange("id-tpp", 1.0),
                                new LanguageRange("en-fr", 0.7),
                                new LanguageRange("en-fx", 0.7),
                                new LanguageRange("en-x-tp", 0.6),
                                new LanguageRange("de-de", 0.5),
                                new LanguageRange("de-dd", 0.5),
                                new LanguageRange("iw", 0.4),
                                new LanguageRange("he", 0.4),
                                new LanguageRange("in-tp", 0.2),
                                new LanguageRange("id-tl", 0.2),
                                new LanguageRange("id-tp", 0.2),
                                new LanguageRange("in-tl", 0.2),
                                new LanguageRange("fr-tp-x-foo", 0.1),
                                new LanguageRange("fr-tl-x-foo", 0.1))},
                {"Accept-Language: hak-CN;q=0.8, no-bok-NO;q=0.9, no-nyn, cmn-CN;q=0.1",
                        List.of(new LanguageRange("no-nyn", 1.0),
                                new LanguageRange("nn", 1.0),
                                new LanguageRange("no-bok-no", 0.9),
                                new LanguageRange("nb-no", 0.9),
                                new LanguageRange("hak-CN", 0.8),
                                new LanguageRange("zh-hakka-CN", 0.8),
                                new LanguageRange("i-hak-CN", 0.8),
                                new LanguageRange("zh-hak-CN", 0.8),
                                new LanguageRange("cmn-CN", 0.1),
                                new LanguageRange("zh-guoyu-CN", 0.1),
                                new LanguageRange("zh-cmn-CN", 0.1))},
                {"Accept-Language: rki;q=0.4, no-bok-NO;q=0.9, ccq;q=0.5",
                        List.of(new LanguageRange("no-bok-no", 0.9),
                                new LanguageRange("nb-no", 0.9),
                                new LanguageRange("rki", 0.4),
                                new LanguageRange("ybd", 0.4),
                                new LanguageRange("ccq", 0.4))},
        };
    }

    @DataProvider(name = "LRParseIAEData")
    Object[][] LRParseIAEData() {
        return new Object[][] {
                // Ranges
                {""},
                {"ja;q=3"},
        };
    }

    @DataProvider(name = "LRMapEquivalentsData")
    Object[][] LRMapEquivalentsData() {
        return new Object[][] {
                // Ranges, Map, Expected result
                {LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4"),
                        new HashMap<>(),
                        LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4")},
                {LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4"),
                        null,
                        LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4")},
                {LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4"),
                        new LinkedHashMap<String, List<String>>() {
                            {
                                put("ja", List.of("ja", "ja-Hira"));
                                put("zh", List.of("zh-Hans", "zh-Hans-CN", "zh-CN"));
                                put("zh-TW", List.of("zh-TW", "zh-Hant"));
                                put("es", null);
                                put("en", List.of());
                                put("zh-HK", List.of("de"));
                            }
                        },
                        List.of(new LanguageRange("zh-hans", 1.0),
                                new LanguageRange("zh-hans-cn", 1.0),
                                new LanguageRange("zh-cn", 1.0),
                                new LanguageRange("de", 1.0),
                                new LanguageRange("ar", 0.9),
                                new LanguageRange("zh-tw", 0.8),
                                new LanguageRange("zh-hant", 0.8),
                                new LanguageRange("ja-jp", 0.2),
                                new LanguageRange("ja-hira-jp", 0.2))},
        };
    }

    @DataProvider(name = "LFilterData")
    Object[][] LFilterData() {
        return new Object[][] {
                // Range, LanguageTags, FilteringMode, Expected locales
                {"ja-JP, fr-FR", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP",
                        EXTENDED_FILTERING, "ja-JP-hepburn, ja-Latn-JP"},
                {"ja-*-JP, fr-FR", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP",
                        EXTENDED_FILTERING, "ja-JP-hepburn, ja-Latn-JP"},
                {"ja-*-JP, fr-FR, de-de;q=0.2", "de-DE, en, ja-JP-hepburn, de-de, fr, he, ja-Latn-JP",
                        AUTOSELECT_FILTERING, "ja-JP-hepburn, ja-Latn-JP, de-DE"},
                {"ja-JP, fr-FR, de-de;q=0.2", "de-DE, en, ja-JP-hepburn, de-de, fr, he, ja-Latn-JP",
                        AUTOSELECT_FILTERING, "ja-JP-hepburn, de-DE"},
                {"en;q=0.2, ja-*-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP",
                        IGNORE_EXTENDED_RANGES, "en"},
                {"en;q=0.2, ja-*-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP",
                        MAP_EXTENDED_RANGES, "ja-JP-hepburn, en"},
                {"en;q=0.2, ja-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP",
                        REJECT_EXTENDED_RANGES, "ja-JP-hepburn, en"},
                {"en;q=0.2, ja-*-JP, fr-JP", "", REJECT_EXTENDED_RANGES, ""},
        };
    }

    @DataProvider(name = "LFilterNPEData")
    Object[][] LFilterNPEData() {
        return new Object[][] {
                // Range, LanguageTags, FilteringMode
                {"en;q=0.2, ja-*-JP, fr-JP", null, REJECT_EXTENDED_RANGES},
                {null, "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", REJECT_EXTENDED_RANGES},
        };
    }

    @DataProvider(name = "LFilterTagsData")
    Object[][] LFilterTagsData() {
        return new Object[][] {
                // Range, LanguageTags, FilteringMode, Expected language tags
                {"fr-FR, fr-BG;q=0.8, *;q=0.5, en;q=0", "en-US, fr-FR, fr-CA, fr-BG",
                        null, "fr-FR, fr-BG, fr-CA"},
                {"fr-FR, fr-*-BG;q=0.8, *;q=0.5, en;q=0", "en-US, fr-FR, fr-CA, fr-BG",
                        null, "fr-FR, fr-BG, fr-CA"},

                {"en;q=0.2, *;q=0.6, ja", "de-DE, en, ja-JP-hepburn, fr-JP, he",
                        null, "ja-JP-hepburn, de-DE, en, fr-JP, he"},
                {"en;q=0.2, ja-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he",
                        null, "ja-JP-hepburn, en"},
                {"en;q=0.2, ja-JP, fr-JP, iw", "de-DE, he, en, ja-JP-hepburn, fr, he-IL",
                        null, "ja-JP-hepburn, he, he-IL, en"},
                {"en;q=0.2, ja-JP, fr-JP, he", "de-DE, en, ja-JP-hepburn, fr, iw-IL",
                        null, "ja-JP-hepburn, iw-IL, en"},

                {"de-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        null, "de-DE, de-DE-x-goethe"},
                {"de-*-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        null,
                        "de-DE, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                                + "de-Latn-DE-1996, de-Deva-DE"},

                {"de-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        EXTENDED_FILTERING,
                        "de-DE, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE"},
                {"de-*-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        EXTENDED_FILTERING,
                        "de-DE, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE"},

                {"de-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        IGNORE_EXTENDED_RANGES,
                        "de-DE, de-DE-x-goethe"},
                {"de-*-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        IGNORE_EXTENDED_RANGES,
                        ""},

                {"de-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        MAP_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"},
                {"de-*-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        MAP_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"},

                {"de-DE", "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                        + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva",
                        REJECT_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"},

                // The next test in this chain is in testLFilterTagsIAE.
        };
    }

    @DataProvider(name = "LLookupData")
    Object[][] LLookupData() {
        return new Object[][] {
                // Range, LanguageTags, Expected locale
                {"en;q=0.2, *-JP;q=0.6, iw", "de-DE, en, ja-JP-hepburn, fr-JP, he", "he"},
                {"en;q=0.2, *-JP;q=0.6, iw", "de-DE, he-IL, en, iw", "he"},
                {"en;q=0.2, ja-*-JP-x-foo;q=0.6, iw", "de-DE, fr, en, ja-Latn-JP", "ja-Latn-JP"},
        };
    }

    @DataProvider(name = "LLookupTagData")
    Object[][] LLookupTagData() {
        return new Object[][] {
                // Range, LanguageTags, Expected language tag
                {"en, *", "es, de, ja-JP", null},
                {"en;q=0.2, *-JP", "de-DE, en, ja-JP-hepburn, fr-JP, en-JP", "fr-JP"},
                {"en;q=0.2, ar-MO, iw", "de-DE, he, fr-JP", "he"},
                {"en;q=0.2, ar-MO, he", "de-DE, iw, fr-JP", "iw"},
                {"de-DE-1996;q=0.8, en;q=0.2, iw;q=0.9, zh-Hans-CN;q=0.7", "de-DE, zh-CN, he, iw, fr-JP", "iw"},
                {"de-DE-1996;q=0.8, en;q=0.2, he;q=0.9, zh-Hans-CN;q=0.7", "de-DE, zh-CN, he, iw, fr-JP", "he"},
        };
    }

    @Test
    public void testLRConstants() {
        assertEquals(MIN_WEIGHT, 0.0, "    MIN_WEIGHT should be 0.0 but got "
                + MIN_WEIGHT);
        assertEquals(MAX_WEIGHT, 1.0, "    MAX_WEIGHT should be 1.0 but got "
                + MAX_WEIGHT);
    }

    @Test(dataProvider = "LRConstructorData")
    public void testLRConstructors(String range, double weight) {
        LanguageRange lr;
        if (weight == MAX_WEIGHT) {
            lr = new LanguageRange(range);
        } else {
            lr = new LanguageRange(range, weight);
        }
        assertEquals(lr.getRange(), range.toLowerCase(Locale.ROOT),
                "    LR.getRange() returned unexpected value. Expected: "
                        + range.toLowerCase(Locale.ROOT) + ", got: " + lr.getRange());
        assertEquals(lr.getWeight(), weight,
                "    LR.getWeight() returned unexpected value. Expected: "
                        + weight + ", got: " + lr.getWeight());
    }

    @Test(dataProvider = "LRConstructorNPEData", expectedExceptions = NullPointerException.class)
    public void testLRConstructorNPE(String range, double weight) {
        if (weight == MAX_WEIGHT) {
            new LanguageRange(range);
        } else {
            new LanguageRange(range, weight);
        }
    }

    @Test(dataProvider = "LRConstructorIAEData", expectedExceptions = IllegalArgumentException.class)
    public void testLRConstructorIAE(String range, double weight) {
        if (weight == MAX_WEIGHT) {
            new LanguageRange(range);
        } else {
            new LanguageRange(range, weight);
        }
    }

    @Test
    public void testLREquals() {
        LanguageRange lr1 = new LanguageRange("ja", 1.0);
        LanguageRange lr2 = new LanguageRange("ja");
        LanguageRange lr3 = new LanguageRange("ja", 0.1);
        LanguageRange lr4 = new LanguageRange("en", 1.0);

        assertEquals(lr1, lr2, "    LR(ja, 1.0).equals(LR(ja)) should return true.");
        assertNotEquals(lr1, lr3, "    LR(ja, 1.0).equals(LR(ja, 0.1)) should return false.");
        assertNotEquals(lr1, lr4, "    LR(ja, 1.0).equals(LR(en, 1.0)) should return false.");
        assertNotNull(lr1, "    LR(ja, 1.0).equals(null) should return false.");
        assertNotEquals(lr1, "", "    LR(ja, 1.0).equals(\"\") should return false.");
    }

    @Test(dataProvider = "LRParseData")
    public void testLRParse(String ranges, List<LanguageRange> expected) {
        assertEquals(LanguageRange.parse(ranges), expected,
                "    LR.parse(" + ranges + ") test failed.");
    }

    @Test(expectedExceptions = NullPointerException.class)
    public void testLRParseNPE() {
        LanguageRange.parse(null);
    }

    @Test(dataProvider = "LRParseIAEData", expectedExceptions = IllegalArgumentException.class)
    public void testLRParseIAE(String ranges) {
        LanguageRange.parse(ranges);
    }

    @Test(dataProvider = "LRMapEquivalentsData")
    public void testLRMapEquivalents(List<Locale.LanguageRange> priorityList,
            Map<String,List<String>> map, List<LanguageRange> expected) {
        assertEquals(LanguageRange.mapEquivalents(priorityList, map), expected,
                "    LR.mapEquivalents() test failed.");
    }

    @Test(expectedExceptions = NullPointerException.class)
    public void testLRMapEquivalentsNPE() {
        LanguageRange.mapEquivalents(null, Map.of("ja", List.of("ja", "ja-Hira")));
    }

    @Test(dataProvider = "LFilterData")
    public void testLFilter(String ranges, String tags, FilteringMode mode, String expectedLocales) {
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        List<Locale> tagList = generateLocales(tags);
        String actualLocales =
                showLocales(Locale.filter(priorityList, tagList, mode));
        assertEquals(actualLocales, expectedLocales, showErrorMessage("    L.Filter(" + mode + ")",
                ranges, tags, expectedLocales, actualLocales));
    }

    @Test(dataProvider = "LFilterNPEData", expectedExceptions = NullPointerException.class)
    public void testLFilterNPE(String ranges, String tags, FilteringMode mode) {
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        List<Locale> tagList = generateLocales(tags);
        showLocales(Locale.filter(priorityList, tagList, mode));
    }

    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testLFilterIAE() {
        String ranges = "en;q=0.2, ja-*-JP, fr-JP";
        String tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        List<Locale> tagList = generateLocales(tags);
        showLocales(Locale.filter(priorityList, tagList, REJECT_EXTENDED_RANGES));
    }

    @Test(dataProvider = "LFilterTagsData")
    public void testLFilterTags(String ranges, String tags, FilteringMode mode, String expectedTags) {
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        List<String> tagList = generateLanguageTags(tags);
        String actualTags;
        if (mode == null) {
            actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList));
        } else {
            actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList, mode));
        }
        assertEquals(actualTags, expectedTags,
                showErrorMessage("    L.FilterTags(" + (mode != null ? mode : "") + ")",
                        ranges, tags, expectedTags, actualTags));
    }

    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testLFilterTagsIAE() {
        String ranges = "de-*-DE";
        String tags = "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
                + "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva";
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        showLanguageTags(Locale.filterTags(priorityList, generateLanguageTags(tags), REJECT_EXTENDED_RANGES));
    }

    @Test(dataProvider = "LLookupData")
    public void testLLookup(String ranges, String tags, String expectedLocale) {
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        List<Locale> localeList = generateLocales(tags);
        String actualLocale =
                Locale.lookup(priorityList, localeList).toLanguageTag();
        assertEquals(actualLocale, expectedLocale, showErrorMessage("    L.Lookup()",
                ranges, tags, expectedLocale, actualLocale));
    }

    @Test(dataProvider = "LLookupTagData")
    public void testLLookupTag(String ranges, String tags, String expectedTag) {
        List<LanguageRange> priorityList = LanguageRange.parse(ranges);
        List<String> tagList = generateLanguageTags(tags);
        String actualTag = Locale.lookupTag(priorityList, tagList);
        assertEquals(actualTag, expectedTag, showErrorMessage("    L.LookupTag()",
                ranges, tags, expectedTag, actualTag));
    }

    private static List<Locale> generateLocales(String tags) {
        if (tags == null) {
            return null;
        }

        List<Locale> localeList = new ArrayList<>();
        if (tags.equals("")) {
            return localeList;
        }
        String[] t = tags.split(", ");
        for (String tag : t) {
            localeList.add(Locale.forLanguageTag(tag));
        }
        return localeList;
    }

    private static List<String> generateLanguageTags(String tags) {
        List<String> tagList = new ArrayList<>();
        String[] t = tags.split(", ");
        for (String tag : t) {
            tagList.add(tag);
        }
        return tagList;
    }

    private static String showLanguageTags(List<String> tags) {
        StringBuilder sb = new StringBuilder();

        Iterator<String> itr = tags.iterator();
        if (itr.hasNext()) {
            sb.append(itr.next());
        }
        while (itr.hasNext()) {
            sb.append(", ");
            sb.append(itr.next());
        }

        return sb.toString().trim();
    }

    private static String showLocales(List<Locale> locales) {
        StringBuilder sb = new StringBuilder();

        Iterator<Locale> itr = locales.iterator();
        if (itr.hasNext()) {
            sb.append(itr.next().toLanguageTag());
        }
        while (itr.hasNext()) {
            sb.append(", ");
            sb.append(itr.next().toLanguageTag());
        }

        return sb.toString().trim();
    }

    private static String showErrorMessage(String methodName,
            String priorityList,
            String tags,
            String expectedTags,
            String actualTags) {
        return "Incorrect " + methodName + " result."
                + "  Priority list  :  " + priorityList
                + "  Language tags  :  " + tags
                + "  Expected value : " + expectedTags
                + "  Actual value   : " + actualTags;
    }
}