├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── google
│ └── testing
│ └── i18n
│ └── sanitycheck
│ ├── SanityCheckProcessor.java
│ ├── checkers
│ ├── Checker.java
│ ├── CommonTokenizationChecker.java
│ ├── DateStringFormatProducer.java
│ ├── DateTimePeriodChecker.java
│ ├── DateTimeStringFormatProducer.java
│ ├── IgnoreChecker.java
│ ├── LocaleChecker.java
│ ├── NumberChecker.java
│ ├── OrderingChecker.java
│ ├── TimeDateChecker.java
│ ├── TimeStringFormatProducer.java
│ ├── TimeZoneChecker.java
│ └── TokenizationChecker.java
│ └── parser
│ ├── Parser.java
│ ├── ParserException.java
│ └── Placeholder.java
└── test
└── java
└── com
└── google
└── testing
└── i18n
└── sanitycheck
├── AppTest.java
├── SanityCheckersTests.java
├── TestUtils.java
├── checkers
├── DateTimePeriodCheckerTest.java
├── LocaleCheckerTest.java
├── NumberCheckerTest.java
├── OrderingCheckerTest.java
├── TimeDateCheckerTest.java
├── TimeZoneCheckerTest.java
└── TokenizationCheckerTest.java
└── parser
└── ParserTest.java
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | i18n-sanitychecker is a library that allows developer to write unit-test for
2 | locale-sensitive functions without relying on "golden data".
3 |
4 | i18n-sanitychecker provides only one public method to match actual strings
5 | against expected patterns. Patterns may contain placeholders for date, time,
6 | numbers, lists. The method behaves similar to other assert methods of JUnit.
7 |
8 | #Usage
9 |
10 | SanityCheckProcessor.assertI18nSanityCheck(pattern, actual_string, locale);
11 |
12 |
13 | #Examples
14 |
15 | 1. Check that DOM element contains date and time in any format for Japanese:
16 | ```java
17 | SanityCheckProcessor.assertI18nSanityCheck("{datetime}",
18 | dom.getSingleNodeText("//abbr[@class='timestamp']"), ULocale.JAPAN);
19 | ```
20 | 2. Check that string contains date in any format suitable for German:
21 | ```java
22 | SanityCheckProcessor.assertI18nSanityCheck(
23 | "Todays is {date}", formatted_string, Locale.GERMAN);
24 | ```
25 | 3. Check that value is a date formatted according the pattern:
26 | ```java
27 | SanityCheckProcessor.assertI18nSanityCheck(
28 | "{datetime:{pattern:'dd-MM-yy'}}", value, currentLocale)
29 | ```
30 |
31 | #Placeholders
32 |
33 | Placeholder | Description
34 | ------------|------------
35 | {ignore} | Ignores everything within this placeholder.
36 | {time} | Checks if formatted time satisfies ICU standards.
37 | {date} | Checks if formatted date satisfies the ICU standards
38 | {datetime} | Merges 2 and 3 together. Has same optional parameters.
39 | {number} | Checks if formatted number satisfies the ICU standards. ould be used to check currency, rational, scientific, numbers and spelled numbers.
40 | {locale} | Checks if locale code matches.
41 | {timezone} | Checks if timezone matches.
42 | {sorting} | Checks if the elements within the placeholder were sorted correctly according to the locale rules.
43 | {period} | checks if date time period is correct.
44 |
45 | #Additional parameters for placeholders
46 | Parameter | Applies To | Description
47 | ----------|----------------------------------------|------------
48 | pattern | date, time, datetime | ICU formatting pattern (see ICU documentation for details).
49 | skeleton | date, time, datetime | ICU skeleton - less restrictive than pattern
50 | value | date, time, datetime, number | actual value in numeric locale-independent format. For date and time - milliseconds since Jan 1, 1970
51 | lenient | date, time, datetime, number, timezone | Relax format validation rules
52 | strict | sorting | Apply more strict sorting rules (see ICU documentation)
53 |
54 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.google.testing.i18n.sanitycheck
5 | sanitychecker
6 | jar
7 | 2.0-SNAPSHOT
8 | sanitychecker
9 | http://maven.apache.org
10 |
11 |
12 | junit
13 | junit
14 | 4.11
15 |
16 |
17 | com.ibm.icu
18 | icu4j
19 | 54.1
20 |
21 |
22 | com.google.guava
23 | guava
24 | 18.0
25 |
26 |
27 | com.google.code.findbugs
28 | jsr305
29 | 3.0.0
30 |
31 |
32 | com.google.code.gson
33 | gson
34 | 2.3
35 |
36 |
37 | org.mockito
38 | mockito-all
39 | 1.9.5
40 | test
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/SanityCheckProcessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.testing.i18n.sanitycheck.checkers.Checker;
20 | import com.google.testing.i18n.sanitycheck.parser.Parser;
21 | import com.google.testing.i18n.sanitycheck.parser.ParserException;
22 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
23 |
24 | import com.ibm.icu.util.ULocale;
25 |
26 | import java.util.List;
27 | import java.util.Locale;
28 |
29 | import javax.annotation.Nullable;
30 |
31 | /**
32 | * Utility methods that perform the sanity check and the comparison of the given strings.
33 | * One basic example of usage:
34 | *
35 | *
36 | * String myOutputString = myGeneratorMethod(); // method that generates "Today is 11/30/05"
37 | * SanityCheckProcessor.assertI18nSanityCheck("Today is {datetime}", myOutputString, ULocale.US);
38 | *
39 | */
40 | public final class SanityCheckProcessor {
41 |
42 | private static final Parser PARSER = new Parser();
43 |
44 | /** Do not instantiate */
45 | private SanityCheckProcessor() {}
46 |
47 | /**
48 | * Asserts that a given {@code actual} value satisfies a given {@code expected} pattern for a
49 | * given locale.
50 | *
51 | * @param expected pattern-based string
52 | * @param actual string that you want to test
53 | */
54 | public static void assertI18nSanityCheck(String expected, String actual) {
55 | assertI18nSanityCheck(null, expected, actual, (ULocale) null);
56 | }
57 |
58 | /**
59 | * Asserts that a given {@code actual} value satisfies a given {@code expected} pattern for a
60 | * given locale.
61 | *
62 | * @param expected pattern-based string
63 | * @param actual string that you want to test
64 | * @param locale to test with. If null, then default en_US locale is used
65 | */
66 | public static void assertI18nSanityCheck(String expected, String actual,
67 | @Nullable ULocale locale) {
68 | assertI18nSanityCheck(null, expected, actual, locale);
69 | }
70 |
71 | /**
72 | * Asserts that a given {@code actual} value satisfies a given {@code expected} pattern for a
73 | * given locale.
74 | *
75 | * @param expected pattern-based string
76 | * @param actual string that you want to test
77 | * @param locale to test with. If null, then default en_US locale is used
78 | */
79 | public static void assertI18nSanityCheck(String expected, String actual,
80 | @Nullable Locale locale) {
81 | assertI18nSanityCheck(null, expected, actual, ULocale.forLocale(locale));
82 | }
83 |
84 | /**
85 | * Asserts that a given {@code actual} value satisfies a given {@code expected} pattern for a
86 | * given locale.
87 | *
88 | * @param message custom message to be shown instead of the automatically produced one. The
89 | * automatic one would stored in the 'cause' part of the thrown exception
90 | * @param expected pattern-based string
91 | * @param actual string that you want to test
92 | * @param locale to test with. If null, then default en_US locale is used
93 | */
94 | public static void assertI18nSanityCheck(@Nullable String message, String expected, String actual,
95 | @Nullable Locale locale) {
96 | assertI18nSanityCheck(message, expected, actual, ULocale.forLocale(locale));
97 | }
98 |
99 | /**
100 | * Asserts that a given {@code actual} value satisfies a given {@code expected} pattern for a
101 | * given locale.
102 | *
103 | * @param message custom message to be shown instead of the automatically produced one. The
104 | * automatic one would stored in the 'cause' part of the thrown exception
105 | * @param expected pattern-based string
106 | * @param actual string that you want to test
107 | * @param locale to test with. If null, then default en_US locale is used
108 | */
109 | public static void assertI18nSanityCheck(@Nullable String message, String expected, String actual,
110 | @Nullable ULocale locale) {
111 | Preconditions.checkNotNull(expected);
112 | Preconditions.checkNotNull(actual);
113 | locale = locale != null ? locale : ULocale.US;
114 | try {
115 | // Parses an original text with respect to the expected pattern and returns the list of the
116 | // Placeholder entities as a result.
117 | List parsedPattern = PARSER.parse(actual, expected);
118 | // Walks through all parsed tokens and performs sanity check of each one of them.
119 | for (Placeholder placeholder : parsedPattern) {
120 | ULocale placeholderLocale = locale;
121 | if (placeholder.getLocale() != null) {
122 | placeholderLocale = new ULocale(placeholder.getLocale());
123 | }
124 | Checker.CHECKERS.get(placeholder.getName()).check(placeholder, placeholderLocale, message);
125 | }
126 | } catch (ParserException e) {
127 | if (message.isEmpty()) {
128 | throw new AssertionError(e.getMessage(), e);
129 | } else {
130 | throw new AssertionError(message, e);
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/Checker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.collect.ImmutableMap;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.util.ULocale;
22 |
23 | import javax.annotation.Nullable;
24 |
25 | /**
26 | * An object that performs sanity check of formatted strings with respect to the a locale. The
27 | * checker object is a singleton within the system, so implementations should not hold any state
28 | * information.
29 | */
30 | public interface Checker {
31 |
32 | /** Sanity checkers that are registered in the system. */
33 | public static final ImmutableMap CHECKERS = ImmutableMap
34 | .builder()
35 | .put("ignore", new IgnoreChecker())
36 | .put("time", new TimeDateChecker(new TimeStringFormatProducer()))
37 | .put("date", new TimeDateChecker(new DateStringFormatProducer()))
38 | .put("datetime", new TimeDateChecker(new DateTimeStringFormatProducer()))
39 | .put("number", new NumberChecker())
40 | .put("timezone", new TimeZoneChecker())
41 | .put("locale", new LocaleChecker())
42 | .put("sorting", new OrderingChecker())
43 | .put("tokenization", new TokenizationChecker())
44 | .put("period", new DateTimePeriodChecker())
45 | .build();
46 |
47 | /**
48 | * Checks if a given {@link Placeholder} contains validly formatted information for a given
49 | * locale.
50 | *
51 | * @param target {@link Placeholder} instance that contains complete information about a formatted
52 | * string that should be checked
53 | * @param locale to perform sanity check with respect to
54 | * @param message is a user preferred message for errors
55 | * @throws AssertionError thrown if check is failed
56 | */
57 | public void check(Placeholder target, ULocale locale, @Nullable String message)
58 | throws AssertionError;
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/CommonTokenizationChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.collect.ImmutableList;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.util.ULocale;
22 |
23 | import org.junit.Assert;
24 |
25 | import java.util.List;
26 |
27 | /**
28 | * An abstract class that provides the common logic for checkers that work with a list of tokenized
29 | * strings. It tokenizes an input string by means of the default or user provided delimiter, builds
30 | * a list from tokens and sends it to the abstract method {@link #makeCheck} for further validation.
31 | */
32 | public abstract class CommonTokenizationChecker implements Checker {
33 |
34 | private static final ImmutableList DEFAULT_SPLITTERS =
35 | ImmutableList.of(System.getProperty("line.separator"), "\\;", "\\,", "\\ ");
36 |
37 | @Override
38 | public void check(Placeholder target, ULocale locale, String message) {
39 | String splitter = target.getSplitter();
40 | String[] tokens = null;
41 | if (splitter != null) {
42 | tokens = target.getActualContent().split(splitter);
43 | } else {
44 | List splitters = getSplitters();
45 | int longestLength = 0;
46 | for (int i = 0; i < splitters.size(); i++) {
47 | String[] tmpTokens = target.getActualContent().split(splitters.get(i));
48 | if (longestLength < tmpTokens.length) {
49 | longestLength = tmpTokens.length;
50 | tokens = tmpTokens;
51 | }
52 | }
53 | }
54 | String errorMessage = message != null ? message
55 | : "No possible splitters were detected. "
56 | + "Please specify splitter explicitly by means of placeholder parameter \"splitter\"";
57 | Assert.assertNotNull(errorMessage, tokens);
58 |
59 | makeCheck(target, ImmutableList.copyOf(tokens), locale, message);
60 | }
61 |
62 | /**
63 | * Returns the list of the string splitters for tokenization.
64 | *
65 | * @return should return the list of splitters actual for this checker
66 | */
67 | protected List getSplitters() {
68 | return DEFAULT_SPLITTERS;
69 | }
70 |
71 | /**
72 | * Performs the sanity check of the list of the tokenized strings.
73 | *
74 | * @param target {@link Placeholder} instance that contains complete information about formatted
75 | * string that should be checked and some modifiers
76 | * @param tokenizedInput list of strings to be checked
77 | */
78 | protected abstract void makeCheck(Placeholder target, ImmutableList tokenizedInput,
79 | ULocale locale, String message);
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/DateStringFormatProducer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.collect.ImmutableList;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.text.DateFormat;
22 | import com.ibm.icu.util.ULocale;
23 |
24 | /**
25 | * Producer of all possible {@link DateFormat}s for date.
26 | * See {@link DateFormat#getDateInstance}.
27 | */
28 | class DateStringFormatProducer implements TimeDateChecker.DateFormatProducer {
29 |
30 | private static final ImmutableList FORMATS = ImmutableList.of(DateFormat.SHORT,
31 | DateFormat.MEDIUM,
32 | DateFormat.LONG,
33 | DateFormat.FULL,
34 | DateFormat.RELATIVE_FULL,
35 | DateFormat.RELATIVE_LONG,
36 | DateFormat.RELATIVE_MEDIUM,
37 | DateFormat.RELATIVE_SHORT);
38 |
39 | @Override
40 | public ImmutableList get(Placeholder target, ULocale locale) {
41 | ImmutableList.Builder result = ImmutableList.builder();
42 | for (Integer type : FORMATS) {
43 | DateFormat dateFormat = DateFormat.getDateInstance(type, locale);
44 | dateFormat.setLenient(target.isLenient());
45 | result.add(dateFormat);
46 | }
47 | return result.build();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/DateTimePeriodChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import static org.junit.Assert.assertEquals;
19 | import static org.junit.Assert.assertNotNull;
20 |
21 | import com.google.common.collect.ImmutableList;
22 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
23 |
24 | import com.ibm.icu.util.ULocale;
25 |
26 | /** An object that performs sanity check of a formatted date time period string. */
27 | class DateTimePeriodChecker implements Checker {
28 |
29 | private static final ImmutableList DEFAULT_SPLITTERS =
30 | ImmutableList.of("\\-", "\\,", "\\ ");
31 |
32 | private final TimeDateChecker timeDateChecker =
33 | new TimeDateChecker(new DateTimeStringFormatProducer());
34 |
35 | @Override
36 | public void check(Placeholder target, ULocale locale, String message) throws AssertionError {
37 | String splitter = null;
38 | if (target.getSplitter() != null) {
39 | splitter = target.getSplitter();
40 | }
41 | String[] tokens = null;
42 | if (splitter != null) {
43 | tokens = target.getActualContent().split(splitter);
44 | } else {
45 | for (int i = 0; i < DEFAULT_SPLITTERS.size(); i++) {
46 | String[] tmpTokens = target.getActualContent().split(DEFAULT_SPLITTERS.get(i));
47 | if (tmpTokens.length == 2) {
48 | tokens = tmpTokens;
49 | break;
50 | }
51 | }
52 | }
53 |
54 | message = message != null ? message
55 | : String.format("Possible splitters for period %s were not located. "
56 | + "Please specify splitter explicitly by means of placeholder parameter \"splitter\"",
57 | target.getActualContent());
58 | assertNotNull(message, tokens);
59 | assertEquals(message, 2, tokens.length);
60 |
61 | for (String datePart : tokens) {
62 | Placeholder datePartPlaceholder =
63 | Placeholder.builder(target).setActualContent(datePart.trim()).build();
64 | timeDateChecker.check(datePartPlaceholder, locale, message);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/DateTimeStringFormatProducer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.collect.ImmutableList;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.text.DateFormat;
22 | import com.ibm.icu.util.ULocale;
23 |
24 | /**
25 | * Producer of all possible {@link DateFormat}s for date and time.
26 | * See {@link DateFormat#getDateTimeInstance}.
27 | */
28 | class DateTimeStringFormatProducer implements TimeDateChecker.DateFormatProducer {
29 |
30 | private static final ImmutableList FORMATS = ImmutableList.of(DateFormat.NONE,
31 | DateFormat.SHORT,
32 | DateFormat.MEDIUM,
33 | DateFormat.LONG,
34 | DateFormat.FULL,
35 | DateFormat.RELATIVE_FULL,
36 | DateFormat.RELATIVE_LONG,
37 | DateFormat.RELATIVE_MEDIUM,
38 | DateFormat.RELATIVE_SHORT);
39 |
40 | @Override
41 | public ImmutableList get(Placeholder target, ULocale locale) {
42 | ImmutableList.Builder result = ImmutableList.builder();
43 | for (Integer dateType : FORMATS) {
44 | for (Integer timeType : FORMATS) {
45 | if (dateType == timeType && dateType == DateFormat.NONE) {
46 | continue;
47 | }
48 | DateFormat dateFormat = DateFormat.getDateTimeInstance(dateType, timeType, locale);
49 | dateFormat.setLenient(target.isLenient());
50 | result.add(dateFormat);
51 | }
52 | }
53 | return result.build();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/IgnoreChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
19 |
20 | import com.ibm.icu.util.ULocale;
21 |
22 | /**
23 | * An object that silently ignores any input data.
24 | */
25 | public class IgnoreChecker implements Checker {
26 |
27 | @Override
28 | public void check(Placeholder target, ULocale locale, String message) throws AssertionError {
29 | return;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/LocaleChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.annotations.VisibleForTesting;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.util.ULocale;
22 |
23 | import org.junit.Assert;
24 |
25 | import javax.annotation.Nullable;
26 |
27 | /** An object that performs sanity check of a formatted locale string. */
28 | public class LocaleChecker implements Checker {
29 |
30 | @Override
31 | public void check(Placeholder target, ULocale locale, String message) {
32 | ULocale parsedLocale = new ULocale(target.getActualContent());
33 | check(parsedLocale, target, message);
34 | }
35 |
36 | @VisibleForTesting
37 | void check(ULocale parsedLocale, Placeholder target, @Nullable String message) {
38 | String value = target.getExpectedValue();
39 | if (value != null) {
40 | message = message != null ? message
41 | : String.format("Expected locale \"%s\" is not equal to %s", value,
42 | parsedLocale.toString());
43 | Assert.assertEquals(message, value, parsedLocale.toString());
44 | } else {
45 | message = message != null ? message
46 | : String.format("Locale \"%s\" is wrong", target.getActualContent());
47 | Assert.assertFalse(message, parsedLocale.getISO3Country().isEmpty()
48 | && parsedLocale.getISO3Language().isEmpty());
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/NumberChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.annotations.VisibleForTesting;
19 | import com.google.common.collect.ImmutableList;
20 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
21 |
22 | import com.ibm.icu.text.NumberFormat;
23 | import com.ibm.icu.text.RuleBasedNumberFormat;
24 | import com.ibm.icu.util.ULocale;
25 |
26 | import org.junit.Assert;
27 |
28 | import java.text.ParseException;
29 | import java.util.List;
30 |
31 | import javax.annotation.Nullable;
32 |
33 | /** An object that performs sanity check of a formatted number. */
34 | public class NumberChecker implements Checker {
35 |
36 | @Override
37 | public void check(Placeholder target, ULocale locale, String message) {
38 | List formatters = ImmutableList.of(NumberFormat.getCurrencyInstance(locale),
39 | NumberFormat.getInstance(locale),
40 | NumberFormat.getIntegerInstance(locale),
41 | NumberFormat.getNumberInstance(locale),
42 | NumberFormat.getPercentInstance(locale),
43 | NumberFormat.getScientificInstance(locale),
44 | new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.SPELLOUT),
45 | new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.ORDINAL),
46 | new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.DURATION),
47 | new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.NUMBERING_SYSTEM));
48 |
49 | check(formatters, target, locale, message);
50 | }
51 |
52 | @VisibleForTesting
53 | void check(List formatters, Placeholder target, ULocale locale,
54 | @Nullable String message) {
55 | boolean passed = false;
56 | for (NumberFormat formatter : formatters) {
57 | try {
58 | if (formatter instanceof RuleBasedNumberFormat) {
59 | ((RuleBasedNumberFormat) formatter).setLenientParseMode(target.isLenient());
60 | }
61 | formatter.setParseStrict(!target.isLenient());
62 | Number parsed = formatter.parse(target.getActualContent());
63 | if (parsed != null) {
64 | String value = target.getExpectedValue();
65 | if (value != null) {
66 | NumberFormat format = NumberFormat.getInstance();
67 | Number expected = format.parse(value);
68 | String errorMessage = message != null ? message
69 | : String.format("Parsed number \"%s\" is not equal to the expected value %s",
70 | parsed, expected);
71 | Assert.assertEquals(errorMessage, expected, parsed);
72 | }
73 | passed = true;
74 | break;
75 | }
76 | } catch (ParseException e) {
77 | // Do nothing.
78 | }
79 | }
80 | String errorMessage = message != null ? message
81 | : String.format("\"%s\" does not satisfies to any numeric format for %s",
82 | target.getActualContent(), locale.toString());
83 | Assert.assertTrue(errorMessage, passed);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/OrderingChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.annotations.VisibleForTesting;
19 | import com.google.common.collect.ImmutableList;
20 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
21 |
22 | import com.ibm.icu.text.Collator;
23 | import com.ibm.icu.text.RuleBasedCollator;
24 | import com.ibm.icu.util.ULocale;
25 |
26 | import org.junit.Assert;
27 |
28 | /** An object that performs sanity check of the sorting order of a given list of strings. */
29 | public class OrderingChecker extends CommonTokenizationChecker {
30 |
31 | @Override
32 | protected void makeCheck(Placeholder target, ImmutableList tokenizedInput, ULocale locale,
33 | String message) {
34 | if (tokenizedInput.size() < 2) {
35 | // Test passed. Nothing to sort.
36 | return;
37 | }
38 |
39 | int strength = Collator.SECONDARY;
40 | if (!target.isLenient()) {
41 | if (!target.isStrict()) {
42 | strength = Collator.TERTIARY;
43 | } else {
44 | strength = Collator.IDENTICAL;
45 | }
46 | }
47 | // Init collators.
48 | Collator collator = Collator.getInstance(locale);
49 | ImmutableList.Builder collators = ImmutableList.builder();
50 | collators.add(collator);
51 | try {
52 | Collator collator2 = (RuleBasedCollator) collator.clone();
53 | collator2.setStrength(strength);
54 | collators.add(collator2);
55 | RuleBasedCollator collator3 = (RuleBasedCollator) collator.clone();
56 | collator3.setStrength(strength);
57 | collator3.setAlternateHandlingShifted(true);
58 | collators.add(collator3);
59 | RuleBasedCollator collator4 = (RuleBasedCollator) collator.clone();
60 | collator4.setStrength(strength);
61 | collator4.setNumericCollation(true);
62 | collators.add(collator4);
63 | RuleBasedCollator collator5 = (RuleBasedCollator) collator.clone();
64 | collator5.setStrength(strength);
65 | collator5.setAlternateHandlingShifted(true);
66 | collator5.setNumericCollation(true);
67 | collators.add(collator5);
68 | } catch (CloneNotSupportedException e) {
69 | // Do nothing.
70 | }
71 | makeCheck(collators.build(), tokenizedInput, locale, message);
72 | }
73 |
74 | @VisibleForTesting
75 | void makeCheck(ImmutableList collators, ImmutableList tokenizedInput,
76 | ULocale locale, String message) {
77 | int position = 0;
78 | boolean failed = false;
79 | if (tokenizedInput != null) {
80 | main: for (int i = 1; i < tokenizedInput.size(); i++) {
81 | position = i;
82 | for (Collator testCollator : collators) {
83 | if (testCollator.compare(tokenizedInput.get(i - 1), tokenizedInput.get(i)) <= 0) {
84 | continue main;
85 | }
86 | }
87 | failed = true;
88 | break;
89 | }
90 | }
91 | String errorMessage = message != null ? message
92 | : String.format("List is not sorted for %s. Should have \"%s\" <= \"%s\" at position %s.",
93 | locale, tokenizedInput.get(position), tokenizedInput.get(position - 1), position);
94 | Assert.assertFalse(errorMessage, failed);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/TimeDateChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.collect.ImmutableList;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.text.DateFormat;
22 | import com.ibm.icu.text.DateTimePatternGenerator;
23 | import com.ibm.icu.text.NumberFormat;
24 | import com.ibm.icu.text.SimpleDateFormat;
25 | import com.ibm.icu.util.ULocale;
26 |
27 | import java.text.ParseException;
28 | import java.util.Date;
29 | import java.util.logging.Level;
30 | import java.util.logging.Logger;
31 |
32 | import javax.annotation.Nullable;
33 |
34 | /** An object that sanity checks a date time related string. */
35 | final class TimeDateChecker implements Checker {
36 |
37 | /**
38 | * A utility that produces the {@link DateFormat}s that correspond to a given {@link Placeholder}
39 | * and {@link ULocale}.
40 | */
41 | static interface DateFormatProducer {
42 |
43 | /** Returns the appropriate {@link DateFormat}s for the given input. */
44 | ImmutableList get(Placeholder target, ULocale locale);
45 | }
46 |
47 | private static final Logger logger = Logger.getLogger(TimeDateChecker.class.getCanonicalName());
48 |
49 | private final DateFormatProducer dateFormatsProducer;
50 |
51 | public TimeDateChecker(DateFormatProducer dateFormatsProducer) {
52 | this.dateFormatsProducer = dateFormatsProducer;
53 | }
54 |
55 | @Override
56 | public void check(Placeholder target, ULocale locale, String message) throws AssertionError {
57 | String pattern = target.getPattern();
58 | String skeleton = target.getSkeleton();
59 | String value = target.getExpectedValue();
60 | Number expected = null;
61 |
62 | if (value != null) {
63 | try {
64 | expected = NumberFormat.getInstance().parse(value);
65 | } catch (ParseException e) {
66 | throw new AssertionError(
67 | String.format("The \"%s\" is an invalid value parameter for the date time checkers. "
68 | + "Please specify numeric representation of date (see Date.getTime())", value));
69 | }
70 | }
71 | if (pattern != null) {
72 | makePatternBasedCheck(target, pattern, locale, expected);
73 | } else if (skeleton != null) {
74 | DateTimePatternGenerator patternGenerator = DateTimePatternGenerator.getInstance(locale);
75 | pattern = patternGenerator.getBestPattern(skeleton);
76 | makePatternBasedCheck(target, pattern, locale, expected);
77 | } else {
78 | makeDateTimeCheck(target, locale, expected);
79 | }
80 | }
81 |
82 | /**
83 | * Checks the given formatted date by means of the {@link DateFormat} instances from
84 | * {@code getFormatters} method. If one of the {@link DateFormat} instances parsed the given
85 | * datetime correctly, then check considered as successful.
86 | *
87 | * @param target instance of the {@link Placeholder}
88 | * @param locale to use during the sanity check
89 | * @param expected is a numeric representation of expected date
90 | * @throws AssertionError
91 | */
92 | private void makeDateTimeCheck(Placeholder target, ULocale locale, @Nullable Number expected)
93 | throws AssertionError {
94 | // This variable is needed to distinguish two error cases: none of formats matches, or there are
95 | // matching formats, but expected value does not match.
96 | boolean matching = false;
97 | for (DateFormat formatter : dateFormatsProducer.get(target, locale)) {
98 | Date result = checkFormat(target, formatter);
99 | if (result != null) {
100 | matching = true;
101 | if (checkExpectedValue(result, expected)) {
102 | return;
103 | }
104 | }
105 | }
106 | String errorMessage;
107 | // Compose error message for assertion based on failure case.
108 | if (matching) {
109 | errorMessage = String.format("The expected value '%d' does not match parsed data.", expected);
110 | } else {
111 | errorMessage = String.format("'%s' does not satisfy any format of locale %s.",
112 | target.getActualContent(), locale.toString());
113 | }
114 | throw new AssertionError(errorMessage);
115 | }
116 |
117 | /**
118 | * Checks the date time format based on the user provided pattern.
119 | *
120 | * @param target instance of the {@link Placeholder}
121 | * @param pattern is a string representation of the date time pattern
122 | * @param locale to use during the sanity check
123 | * @param expected is a numeric representation of expected date
124 | * @throws AssertionError
125 | */
126 | private void makePatternBasedCheck(Placeholder target, String pattern,
127 | ULocale locale, @Nullable Number expected) throws AssertionError {
128 | DateFormat formatter = new SimpleDateFormat(pattern, locale);
129 | formatter.setLenient(target.isLenient());
130 |
131 | Date result = checkFormat(target, formatter);
132 | if (result != null) {
133 | if (!checkExpectedValue(result, expected)) {
134 | throw new AssertionError(String.format(
135 | "Expected value '%s' does not match parsed value '%s' for pattern '%s'.",
136 | expected, result.getTime()));
137 | }
138 | } else {
139 | throw new AssertionError(String.format("'%s' does not satisfy specified pattern '%s'.",
140 | target.getActualContent(), pattern));
141 | }
142 | }
143 |
144 | /**
145 | * Checks the date time format against specificed formatter.
146 | *
147 | * @param target instance of the {@link Placeholder}
148 | * @param formatter is DateFormat instance to check against
149 | * @return a parsed date time if target matches format
150 | */
151 | @Nullable
152 | private Date checkFormat(Placeholder target, DateFormat formatter) {
153 | Date result = null;
154 | try {
155 | result = formatter.parse(target.getActualContent());
156 | if (result != null) {
157 | if (target.isStrict()) {
158 | String formattedDate = formatter.format(result);
159 | if (!formattedDate.equals(target.getActualContent())) {
160 | result = null;
161 | }
162 | }
163 | }
164 | } catch (ParseException e) {
165 | // Just log, result is null by default.
166 | logger.log(Level.FINE, String.format(
167 | "Not critical exception happened during the date time sanity check for input %s",
168 | target.getActualContent()), e);
169 | } catch (UnsupportedOperationException e) {
170 | // Just log. Relative date time parsing is not implemented yet.
171 | logger.log(Level.FINE, "Relative datetime parsing is not implemented yet");
172 | }
173 | return result;
174 | }
175 |
176 | /**
177 | * Checks the date against expected value.
178 | *
179 | * @param date instance of {@link Date}
180 | * @param expected is a numerical representation of expected date
181 | * @return true if date matches expected value, false otherwise
182 | */
183 | private boolean checkExpectedValue(Date date, @Nullable Number expected) {
184 | return (expected == null) || expected.equals(date.getTime());
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/TimeStringFormatProducer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.collect.ImmutableList;
19 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
20 |
21 | import com.ibm.icu.text.DateFormat;
22 | import com.ibm.icu.util.ULocale;
23 |
24 | /**
25 | * Producer of all possible {@link DateFormat}s for time.
26 | * See {@link DateFormat#getTimeInstance}.
27 | */
28 | class TimeStringFormatProducer implements TimeDateChecker.DateFormatProducer {
29 |
30 | private static final ImmutableList FORMATS = ImmutableList.of(DateFormat.SHORT,
31 | DateFormat.MEDIUM,
32 | DateFormat.LONG,
33 | DateFormat.FULL,
34 | DateFormat.RELATIVE_FULL,
35 | DateFormat.RELATIVE_LONG,
36 | DateFormat.RELATIVE_MEDIUM,
37 | DateFormat.RELATIVE_SHORT);
38 |
39 | @Override
40 | public ImmutableList get(Placeholder target, ULocale locale) {
41 | ImmutableList.Builder result = ImmutableList.builder();
42 | for (Integer type : FORMATS) {
43 | DateFormat dateFormat = DateFormat.getTimeInstance(type, locale);
44 | dateFormat.setLenient(target.isLenient());
45 | result.add(dateFormat);
46 | }
47 | return result.build();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/TimeZoneChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
19 |
20 | import com.ibm.icu.text.TimeZoneFormat;
21 | import com.ibm.icu.util.TimeZone;
22 | import com.ibm.icu.util.ULocale;
23 |
24 | import org.junit.Assert;
25 |
26 | import java.text.ParseException;
27 |
28 | /** An object that performs sanity check of a formatted time zone. */
29 | public class TimeZoneChecker implements Checker {
30 |
31 | @Override
32 | public void check(Placeholder target, ULocale locale, String message) {
33 | TimeZoneFormat formatter = TimeZoneFormat.getInstance(locale);
34 | String value = target.getExpectedValue();
35 | try {
36 | TimeZone timeZone = formatter.parse(target.getActualContent());
37 | if (timeZone == null) {
38 | String errorMessage = message != null ? message
39 | : String.format("Timezone \"%s\" is not valid", target.getActualContent());
40 | Assert.fail(errorMessage);
41 | }
42 | if (value != null) {
43 | String errorMessage = message != null ? message
44 | : String.format("Timezone \"%s\" is not equal to the expected timezone %s",
45 | target.getActualContent(), value);
46 | Assert.assertEquals(errorMessage, value, timeZone.getDisplayName());
47 | }
48 | } catch (ParseException e) {
49 | String errorMessage = message != null ? message
50 | : String.format("Timezone \"%s\" is not valid", target.getActualContent());
51 | Assert.fail(errorMessage);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/checkers/TokenizationChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.checkers;
17 |
18 | import com.google.common.annotations.VisibleForTesting;
19 | import com.google.common.collect.ImmutableList;
20 | import com.google.testing.i18n.sanitycheck.parser.Placeholder;
21 |
22 | import com.ibm.icu.text.BreakIterator;
23 | import com.ibm.icu.util.ULocale;
24 |
25 | import org.junit.Assert;
26 |
27 | import java.util.Arrays;
28 |
29 | /**
30 | * An object that validates the correctness of a string's tokenization. Based on ICU
31 | * {@link BreakIterator} class and inherits all its weaknesses. Don't use for ko locale because
32 | * {@link BreakIterator} is not working correctly for this locale.
33 | */
34 | public class TokenizationChecker extends CommonTokenizationChecker {
35 |
36 | @Override
37 | protected void makeCheck(Placeholder target, ImmutableList tokenizedInput, ULocale locale,
38 | String message) {
39 | StringBuilder untokenizedString = new StringBuilder();
40 | String expected = target.getExpectedValue();
41 | if (expected.isEmpty()) {
42 | for (String token : tokenizedInput) {
43 | untokenizedString.append(token);
44 | }
45 | } else {
46 | untokenizedString.append(expected);
47 | }
48 |
49 | ImmutableList tokensFromICU =
50 | getTokens(untokenizedString.toString(), BreakIterator.getWordInstance(locale));
51 | String errorMessage = message != null ? message
52 | : String.format(
53 | "The tokenization %s for the text \"%s\" doesn't appear to be valid for %s."
54 | + "Should be more like: %s", Arrays.toString(tokenizedInput.toArray(new String[0])),
55 | untokenizedString.toString(), locale.getDisplayLanguage(),
56 | Arrays.toString(tokensFromICU.toArray(new String[0])));
57 | Assert.assertEquals(errorMessage, tokensFromICU, tokenizedInput);
58 | }
59 |
60 | /**
61 | * Tokenizes the given text using the {@link BreakIterator}. If the language is not supported or
62 | * the text cannot be tokenized it is returned as is.
63 | *
64 | * @param text The text to be tokenized
65 | * @param breakIterator is an instance of the {@link BreakIterator} class you want to use for
66 | * tokenization
67 | * @return list of tokens extracted from the given text
68 | */
69 | @VisibleForTesting
70 | ImmutableList getTokens(String text, BreakIterator breakIterator) {
71 | breakIterator.setText(text);
72 | ImmutableList.Builder result = ImmutableList.builder();
73 | int start = breakIterator.first();
74 | for (int end = breakIterator.next(); end != BreakIterator.DONE; start = end,
75 | end = breakIterator.next()) {
76 | result.add(text.substring(start, end));
77 | }
78 | return result.build();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/parser/Parser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.parser;
17 |
18 | import com.google.common.collect.ImmutableList;
19 |
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 |
24 | /**
25 | * An object that expects two related strings, actual and expected, as an input and produces
26 | * {@link Placeholder}s as a result.
27 | *
28 | *
29 | * - actual is any string that is produced by a code user wants to test. It should satisfy a
30 | * pattern specified in the expected string. If not, then {@link ParserException} will be thrown.
31 | *
- expected is a placeholder based pattern to parse the actual string.
32 | *
33 | *
34 | * All of the placeholders in the expected string will be extracted, associated to the actual string
35 | * parts and returned as a result during the parsing procedure.
36 | *
37 | * The syntax of the expected string is following:
38 | *
39 | * - It could be a simple string without any special markers, with static text only. In this case
40 | * parser just compares actual and expected strings and throws the {@link ParserException} if they
41 | * are not equal.
42 | *
- It could be a static text mixed with one or more placeholders (Please read more about
43 | * placeholders syntax in the {@link Placeholder} class javadoc). In this case, the parser will try
44 | * to associate specific part of the actual message with any single placeholder from the expected
45 | * string. After that, static parts of the expected message are compared with related parts of the
46 | * actual message.
47 | *
- Even if a part of the expected string satisfies a placeholder syntax, it will be recognized
48 | * as one if and only if this placeholder is listed in the
49 | * {@link com.google.testing.i18n.sanitycheck.checkers.Checker#CHECKERS} map. If not, then this part
50 | * of the expected text is interpreted as a static text.
51 | *
- Regex constructions are legal in the expected string. They will be escaped and interpreted as
52 | * static text.
53 | *
- It is also possible to escape { and } characters by means of \{ and \} constructions.
54 | *
55 | *
56 | */
57 | public class Parser {
58 |
59 | /** Precompiled regular expression to detect placeholders. */
60 | private static final Pattern PLACEHOLDER_PATTERN =
61 | Pattern.compile("((? parse(String actual, String expected) throws ParserException {
80 | Matcher placeholderMatcher = PLACEHOLDER_PATTERN.matcher(expected);
81 | String extractionPatternText = buildExtractionPattern(placeholderMatcher, expected);
82 | placeholderMatcher.reset();
83 | ImmutableList rawPlaceholders = extractPlaceholders(placeholderMatcher);
84 | Pattern extractionPattern = Pattern.compile(extractionPatternText);
85 |
86 | // Extracting actual values from the given expected result.
87 | Matcher extractionMatcher = extractionPattern.matcher(actual);
88 | int extractedGroups = extractionMatcher.groupCount();
89 | ImmutableList.Builder placeholderTokens = ImmutableList.builder();
90 | if (extractionMatcher.find()) {
91 | if (extractedGroups != rawPlaceholders.size()) {
92 | // Should never happen. The number of groups is always equal to the number of placeholders.
93 | throw new ParserException(
94 | String.format("Actual result does not satisfies the expected one."));
95 | }
96 | for (int i = 1; i <= extractedGroups; i++) {
97 | String rawPlaceholder = rawPlaceholders.get(i - 1);
98 | String actualPlaceholderContent = extractionMatcher.group(i);
99 | if (rawPlaceholder.equals(actualPlaceholderContent)) {
100 | continue;
101 | }
102 | placeholderTokens.add(buildPlaceholder(rawPlaceholder, actualPlaceholderContent));
103 | }
104 | return placeholderTokens.build();
105 | }
106 | throw new ParserException(String.format("Actual result does not satisfies the expected one. "
107 | + "Actual text \"%s\" is not parsable by means of the generated pattern \"%s\"", actual,
108 | expected));
109 | }
110 |
111 | /**
112 | * Builds a regular expression based on a given expected string. This regular, if applied to an
113 | * actual string, produces a set of actual values related to any single placeholder in the
114 | * expected string.
115 | *
116 | * @param placeholderMatcher is a given expected string parsed by means of
117 | * {@code PLACEHOLDER_PATTERN} pattern
118 | * @param expected pattern that describes a given {@code actual} data
119 | * @return a regular expression that could be used to parse an actual string
120 | */
121 | private String buildExtractionPattern(Matcher placeholderMatcher, String expected) {
122 | StringBuilder extractionPatternText = new StringBuilder("(?s)(?m)");
123 | int position = 0;
124 | while (placeholderMatcher.find()) {
125 | extractionPatternText.append(Pattern.quote(
126 | replaceEscapedCharacters(expected.substring(position, placeholderMatcher.start()))));
127 | if (placeholderMatcher.hitEnd()) {
128 | extractionPatternText.append(GREEDY_GROUP);
129 | } else {
130 | extractionPatternText.append(LAZY_GROUP);
131 | }
132 |
133 | position = placeholderMatcher.end();
134 | }
135 | extractionPatternText.append(
136 | Pattern.quote(replaceEscapedCharacters(expected.substring(position))));
137 | extractionPatternText.append("$");
138 | return extractionPatternText.toString();
139 | }
140 |
141 | /**
142 | * Goes through a parsed expected string, extracts all placeholders located there and combines
143 | * them to a result list in the same order as they appear in the expected string.
144 | *
145 | * @param placeholderMatcher is a given expected string parsed by means of
146 | * {@code PLACEHOLDER_PATTERN} pattern
147 | * @return an immutable list of all the placeholders presented in the expected string
148 | */
149 | private ImmutableList extractPlaceholders(Matcher placeholderMatcher) {
150 | ImmutableList.Builder result = ImmutableList.builder();
151 | while (placeholderMatcher.find()) {
152 | result.add(replaceEscapedCharacters(placeholderMatcher.group()));
153 | }
154 | return result.build();
155 | }
156 |
157 | /**
158 | * Builds a {@link Placeholder} instance.
159 | *
160 | * @param rawPlaceholder is a string representation of a placeholder. E.g. {date:{lenient:false}}
161 | * @param placeholderContent is a string value from an actual string, that is related to this
162 | * placeholder
163 | * @return new instance of {@link Placeholder}
164 | * @throws ParserException problem happened during the placeholder creation
165 | */
166 | private Placeholder buildPlaceholder(String rawPlaceholder, String placeholderContent)
167 | throws ParserException {
168 | Placeholder placeholder = Placeholder.buildPlaceholderToken(rawPlaceholder, placeholderContent);
169 | if (placeholder != null) {
170 | return placeholder;
171 | }
172 | throw new ParserException(String.format("Actual result doesn't satisfy an expected one. "
173 | + "The placeholder \"%s\" is not valid within the system, and the related text \"%s\" "
174 | + "is not equals to the \"%s\"", rawPlaceholder, placeholderContent, rawPlaceholder));
175 | }
176 |
177 | /**
178 | * Replaces '\{' with '{' and '\}' with '}' in a given string.
179 | *
180 | * @param toReplace string than may contain elements to replace
181 | * @return string with replaced elements
182 | */
183 | private String replaceEscapedCharacters(String toReplace) {
184 | return toReplace.replace("\\{", "{").replace("\\}", "}");
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/parser/ParserException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.parser;
17 |
18 | /**
19 | * An object that represents an exception that would be thrown by {@link Parser} object if parsing
20 | * error happened.
21 | */
22 | public class ParserException extends Exception {
23 |
24 | public ParserException(String message) {
25 | super(message);
26 | }
27 |
28 | public ParserException(String message, Throwable cause) {
29 | super(message, cause);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/google/testing/i18n/sanitycheck/parser/Placeholder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Google Inc.
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 | package com.google.testing.i18n.sanitycheck.parser;
17 |
18 | import com.google.common.base.Preconditions;
19 | import com.google.common.collect.ImmutableMap;
20 | import com.google.common.collect.Maps;
21 | import com.google.gson.Gson;
22 | import com.google.gson.JsonSyntaxException;
23 | import com.google.gson.reflect.TypeToken;
24 | import com.google.testing.i18n.sanitycheck.checkers.Checker;
25 |
26 | import java.lang.reflect.Type;
27 | import java.util.Collections;
28 | import java.util.Map;
29 |
30 | import javax.annotation.Nullable;
31 |
32 | /**
33 | * An immutable object that represents a materialized sanity checkers placeholder like {date} or
34 | * {ignore}.
35 | *
36 | * Contains following information:
37 | *
38 | * - name - which is date for {date} placeholder and etc.
39 | *
- actualContent - actual value related to this placeholder.
40 | *
- params - map of configuration settings for this specific placeholder. E.g. lenient=false pair
41 | * for {date:{lenient:false}}.
42 | *
43 | *
44 | * The placeholder syntax is based on the simplyfied JSON and looks like this:
45 | *
46 | * - {PLACEHOLDER_NAME} is a simple placeholder with the name = PLACEHOLDER_NAME and without
47 | * additional parameters.
48 | *
- {PLACEHOLDER_NAME:{PARAM1_NAME=PARAM1_VALUE}} is a placeholder with the name =
49 | * PLACEHOLDER_NAME and one additional parameter "PARAM1_NAME" with the value "PARAM1_VALUE". It is
50 | * also legal, but not necessary, to surround parameter names and values with "". Having two
51 | * parameters with the same name is illegal.
52 | *
- {PLACEHOLDER_NAME:{PARAM1_NAME=PARAM1_VALUE,PARAM2_NAME=PARAM2_VALUE}} is an example of a
53 | * placeholder with multiple parameters.
54 | *
55 | *
56 | */
57 | public final class Placeholder {
58 |
59 | /**
60 | * An object that is a {@link Placeholder} builder. Think of it as a mutable {@link Placeholder}.
61 | */
62 | public static final class Builder {
63 | private String name;
64 | private String actualContent;
65 | private final Map params = Maps.newHashMap();
66 |
67 | /**
68 | * Creates a new {@link Builder} with the given attributes.
69 | *
70 | * @param name of a placeholder
71 | * @param actualContent of a placeholder
72 | * @param params map of a placeholder
73 | */
74 | private Builder(String name, String actualContent, Map params) {
75 | this.name = Preconditions.checkNotNull(name);
76 | this.actualContent = Preconditions.checkNotNull(actualContent);
77 | this.params.putAll(Preconditions.checkNotNull(params));
78 | }
79 |
80 | /**
81 | * Copy constructor that copies all data from a given placeholder.
82 | *
83 | * @param original is an instance of the {@link Placeholder} class
84 | */
85 | private Builder(Placeholder original) {
86 | this(original.name, original.actualContent, original.params);
87 | }
88 |
89 | /** Sets a name and returns this builder for chaining. */
90 | public Builder setName(String name) {
91 | this.name = Preconditions.checkNotNull(name);
92 | return this;
93 | }
94 |
95 | /** Sets an actual content and returns this builder for chaining. */
96 | public Builder setActualContent(String actualContent) {
97 | this.actualContent = Preconditions.checkNotNull(actualContent);
98 | return this;
99 | }
100 |
101 | /**
102 | * Puts all params from a given map to this placeholder and returns this builder for chaining.
103 | */
104 | public Builder putAllParams(Map params) {
105 | this.params.putAll(Preconditions.checkNotNull(params));
106 | return this;
107 | }
108 |
109 | /** Deletes all params from this placeholder and returns this builder for chaining. */
110 | public Builder clearParams() {
111 | params.clear();
112 | return this;
113 | }
114 |
115 | /** Puts a given locale parameter to the placeholder and returns this builder for chaining. */
116 | public Builder putLocaleParam(String locale) {
117 | params.put(LOCALE_PARAM_NAME, Preconditions.checkNotNull(locale));
118 | return this;
119 | }
120 |
121 | /**
122 | * Puts a given isLenient parameter to the placeholder and returns this builder for chaining.
123 | */
124 | public Builder putLenientParam(boolean isLenient) {
125 | params.put(LENIENT_PARAM_NAME, String.valueOf(isLenient));
126 | return this;
127 | }
128 |
129 | /**
130 | * Puts a given isStrict parameter to the placeholder and returns this builder for chaining.
131 | */
132 | public Builder putStrictParam(boolean isStrict) {
133 | params.put(STRICT_PARAM_NAME, String.valueOf(isStrict));
134 | return this;
135 | }
136 |
137 | /**
138 | * Puts a given expectedValue parameter to the placeholder and returns this builder for
139 | * chaining.
140 | */
141 | public Builder putExpectedValueParam(String expectedValue) {
142 | params.put(VALUE_PARAM_NAME, Preconditions.checkNotNull(expectedValue));
143 | return this;
144 | }
145 |
146 | /**
147 | * Puts a given pattern parameter to the placeholder and returns this builder for chaining.
148 | */
149 | public Builder putPatternParam(String pattern) {
150 | params.put(PATTERN_PARAM_NAME, Preconditions.checkNotNull(pattern));
151 | return this;
152 | }
153 |
154 | /**
155 | * Puts a given skeleton parameter to the placeholder and returns this builder for chaining.
156 | */
157 | public Builder putSkeletonParam(String skeleton) {
158 | params.put(SKELETON_PARAM_NAME, Preconditions.checkNotNull(skeleton));
159 | return this;
160 | }
161 |
162 | /**
163 | * Puts a given splitter parameter to the placeholder and returns this builder for chaining.
164 | */
165 | public Builder putSplitterParam(String splitter) {
166 | params.put(SPLITTER_PARAM_NAME, Preconditions.checkNotNull(splitter));
167 | return this;
168 | }
169 |
170 | /** Build a {@link Placeholder} instance and returns it. */
171 | public Placeholder build() {
172 | return new Placeholder(name, actualContent, ImmutableMap.copyOf(params));
173 | }
174 | }
175 |
176 | /** Params that are accessible by means of getters in the {@link Placeholder} class. */
177 | private static final String LENIENT_PARAM_NAME = "lenient";
178 | private static final String LOCALE_PARAM_NAME = "locale";
179 | private static final String SKELETON_PARAM_NAME = "skeleton";
180 | private static final String SPLITTER_PARAM_NAME = "splitter";
181 | private static final String STRICT_PARAM_NAME = "strict";
182 | private static final String PATTERN_PARAM_NAME = "pattern";
183 | private static final String VALUE_PARAM_NAME = "value";
184 |
185 | private static final Gson GSON = new Gson();
186 |
187 | private final String name;
188 | private final String actualContent;
189 | private final ImmutableMap params;
190 |
191 | /**
192 | * Creates a placeholder token from a given {@code tokenContent}. Could also return null if given
193 | * {@code tokenContext} does not satisfies the placeholder format. Default visibility, because
194 | * normally, should be used only by {@link Parser} class.
195 | *
196 | * @param tokenContent text representation of the placeholder
197 | * @param actualContent content of a placeholder in an expected string
198 | * @return instance of the {@link Placeholder}
199 | */
200 | @Nullable
201 | static final Placeholder buildPlaceholderToken(String tokenContent, String actualContent) {
202 | tokenContent = tokenContent.trim();
203 | // Should never be false.
204 | Preconditions.checkArgument(tokenContent.startsWith("{") && tokenContent.endsWith("}"));
205 | String placeholderName = null;
206 | Map params = Maps.newHashMap();
207 | // Checks if pattern is complex. Looks like {placeholder:{param:value}}.
208 | try {
209 | if (tokenContent.contains(":")) {
210 | Type stringMap = new TypeToken