1 // Copyright 2010 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.search
.checkers
;
5 import com
.google
.appengine
.api
.search
.DateUtil
;
6 import com
.google
.appengine
.api
.search
.query
.ExpressionTreeBuilder
;
7 import com
.google
.apphosting
.api
.search
.DocumentPb
;
8 import com
.google
.common
.base
.Strings
;
10 import org
.antlr
.runtime
.RecognitionException
;
12 import java
.nio
.charset
.Charset
;
13 import java
.util
.Date
;
14 import java
.util
.Locale
;
17 * Provides checks for Field names, language code, and values: text, HTML, atom
21 public final class FieldChecker
{
24 * Checks whether a field name is valid. The field name length must be
25 * between 1 and {@link SearchApiLimits#MAXIMUM_NAME_LENGTH} and it should match
26 * {@link SearchApiLimits#FIELD_NAME_PATTERN}.
28 * @param name the field name to check
29 * @return the checked field name
30 * @throws IllegalArgumentException if the field name is null or empty
31 * or is longer than {@literal SearchApiLimits#MAXIMUM_NAME_LENGTH} or it doesn't
32 * match {@literal #FIELD_NAME_PATTERN}.
34 public static String
checkFieldName(String name
) {
35 return checkFieldName(name
, "field name");
39 * Checks whether a field name is valid. The field name length must be
40 * between 1 and {@link SearchApiLimits#MAXIMUM_NAME_LENGTH} and it should match
41 * {@link SearchApiLimits#FIELD_NAME_PATTERN}.
43 * @param name the field name to check
44 * @param fieldName the name of the Java field name of the class where
46 * @return the checked field name
47 * @throws IllegalArgumentException if the field name is null or empty
48 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
49 * match {@literal #FIELD_NAME_PATTERN}.
51 public static String
checkFieldName(String name
, String fieldName
) {
52 Preconditions
.checkArgument(!Strings
.isNullOrEmpty(name
), "%s cannot be null or empty",
54 Preconditions
.checkArgument(bytesInString(name
) <= SearchApiLimits
.MAXIMUM_NAME_LENGTH
,
55 "%s longer than %d: %s", fieldName
, SearchApiLimits
.MAXIMUM_NAME_LENGTH
, name
);
56 Preconditions
.checkArgument(name
.matches(SearchApiLimits
.FIELD_NAME_PATTERN
),
57 "%s should match pattern %s: %s", fieldName
, SearchApiLimits
.FIELD_NAME_PATTERN
, name
);
62 * Returns the number of bytes in the given string when UTF-8 encoded.
64 static int bytesInString(String str
) {
65 return str
.getBytes(Charset
.forName("UTF-8")).length
;
69 * Returns true if name matches {@link SearchApiLimits#FIELD_NAME_PATTERN}.
71 static boolean nameMatchesPattern(String name
) {
72 return name
.matches(SearchApiLimits
.FIELD_NAME_PATTERN
);
76 * Checks whether a text is valid. A text can be null, or a string between
77 * 0 and {@literal SearchApiLimits.MAXIMUM_TEXT_LENGTH} in length.
79 * @param text the text to check
80 * @return the checked text
81 * @throws IllegalArgumentException if text is too long
83 public static String
checkText(String text
) {
85 Preconditions
.checkArgument(bytesInString(text
) <= SearchApiLimits
.MAXIMUM_TEXT_LENGTH
,
86 "Field text longer than maximum length %d", SearchApiLimits
.MAXIMUM_TEXT_LENGTH
);
92 * Checks whether a html is valid. A html can be null or a string between
93 * 0 and {@literal SearchApiLimits.MAXIMUM_TEXT_LENGTH} in length.
95 * @param html the html to check
96 * @return the checked html
97 * @throws IllegalArgumentException if html is too long
99 public static String
checkHTML(String html
) {
101 Preconditions
.checkArgument(bytesInString(html
) <= SearchApiLimits
.MAXIMUM_TEXT_LENGTH
,
102 "html longer than maximum length %d", SearchApiLimits
.MAXIMUM_TEXT_LENGTH
);
108 * Checks whether an atom is valid. An atom can be null or a string between
109 * 1 and {@literal SearchApiLimits.MAXIMUM_ATOM_LENGTH} in length.
111 * @param atom the atom to check
112 * @return the checked atom
113 * @throws IllegalArgumentException if atom is too long
115 public static String
checkAtom(String atom
) {
117 Preconditions
.checkArgument(bytesInString(atom
) <= SearchApiLimits
.MAXIMUM_ATOM_LENGTH
,
118 "Field atom longer than maximum length %d", SearchApiLimits
.MAXIMUM_ATOM_LENGTH
);
124 * Checks whether a prefix field is valid. A prefix field can be null or a string between
125 * 1 and {@literal SearchApiLimits.MAXIMUM_PREFIX_LENGTH} in length.
127 * @param prefix the prefix to check
128 * @return the checked prefix
129 * @throws IllegalArgumentException if prefix is too long
131 public static String
checkPrefix(String prefix
) {
132 if (prefix
!= null) {
133 Preconditions
.checkArgument(bytesInString(prefix
) <= SearchApiLimits
.MAXIMUM_PREFIX_LENGTH
,
134 "Field prefix longer than maximum length %d", SearchApiLimits
.MAXIMUM_PREFIX_LENGTH
);
140 * Checks whether a number is valid. A number can be null or a value between
141 * {@link SearchApiLimits#MINIMUM_NUMBER_VALUE} and {@link SearchApiLimits#MAXIMUM_NUMBER_VALUE},
144 * @param value the value to check
145 * @return the checked number
146 * @throws IllegalArgumentException if number is out of range
148 public static Double
checkNumber(Double value
) {
150 Preconditions
.checkArgument(SearchApiLimits
.MINIMUM_NUMBER_VALUE
<= value
,
151 String
.format("number value, %f, must be greater than or equal to %f",
152 value
, SearchApiLimits
.MINIMUM_NUMBER_VALUE
));
153 Preconditions
.checkArgument(value
<= SearchApiLimits
.MAXIMUM_NUMBER_VALUE
,
154 String
.format("number value, %f, must be less than or equal to %f",
155 value
, SearchApiLimits
.MAXIMUM_NUMBER_VALUE
));
161 * Checks whether a date is within range. Date is nullable.
163 * @param date the date to check
164 * @return the checked date
165 * @throws IllegalArgumentException if date is out of range
167 public static Date
checkDate(Date date
) throws IllegalArgumentException
{
169 Preconditions
.checkArgument(
170 SearchApiLimits
.MINIMUM_DATE_VALUE
.compareTo(date
) <= 0,
171 String
.format("date %s must be after %s",
172 DateUtil
.formatDateTime(date
),
173 DateUtil
.formatDateTime(SearchApiLimits
.MINIMUM_DATE_VALUE
)));
174 Preconditions
.checkArgument(
175 date
.compareTo(SearchApiLimits
.MAXIMUM_DATE_VALUE
) <= 0,
176 String
.format("date %s must be before %s",
177 DateUtil
.formatDateTime(date
),
178 DateUtil
.formatDateTime(SearchApiLimits
.MAXIMUM_DATE_VALUE
)));
183 private static String
checkExpressionHelper(String expression
, String mode
) {
184 Preconditions
.checkNotNull(expression
, "expression cannot be null");
185 ExpressionTreeBuilder parser
= new ExpressionTreeBuilder();
187 parser
.parse(expression
);
188 } catch (RecognitionException e
) {
189 String message
= String
.format("Failed to parse %s expression '%s': "
190 + "parse error at line %d position %d",
191 mode
, expression
, e
.line
, e
.charPositionInLine
);
192 throw new IllegalArgumentException(message
);
198 * Checks whether a field expression is not null and is parsable.
200 * @param expression the expression to check
201 * @return the checked expression
202 * @throws IllegalArgumentException if the expression is null, or
205 public static String
checkExpression(String expression
) {
206 return checkExpressionHelper(expression
, "field");
210 * Checks whether a sort bexpression is not null and is parsable.
212 * @param expression the expression to check
213 * @return the checked expression
214 * @throws IllegalArgumentException if the expression is null, or
217 public static String
checkSortExpression(String expression
) {
218 return checkExpressionHelper(expression
, "sort");
221 public static DocumentPb
.Field
checkValid(DocumentPb
.Field field
) {
222 checkFieldName(field
.getName());
223 DocumentPb
.FieldValue value
= field
.getValue();
224 switch (value
.getType()) {
226 checkText(value
.getStringValue());
229 checkHTML(value
.getStringValue());
232 checkDate(DateUtil
.deserializeDate(value
.getStringValue()));
235 checkAtom(value
.getStringValue());
238 checkNumber(Double
.parseDouble(value
.getStringValue()));
241 GeoPointChecker
.checkValid(value
.getGeo());
244 checkPrefix(value
.getStringValue());
247 throw new IllegalArgumentException("Unsupported field type " + value
.getType());
253 * Returns a {@link Locale} parsed from the given locale string.
255 * @param locale a string representation of a {@link Locale}
256 * @return a {@link Locale} parsed from the given locale string
257 * @throws IllegalArgumentException if the locale cannot be parsed
259 public static Locale
parseLocale(String locale
) {
260 if (locale
== null) {
263 String
[] parts
= locale
.split("_", 3);
264 if (parts
.length
== 1) {
265 return new Locale(parts
[0]);
267 if (parts
.length
== 2) {
268 return new Locale(parts
[0], parts
[1]);
270 if (parts
.length
== 3) {
271 return new Locale(parts
[0], parts
[1], parts
[2]);
273 throw new IllegalArgumentException("Cannot parse locale " + locale
);