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
.ExpressionTreeBuilder
;
7 import com
.google
.apphosting
.api
.AppEngineInternal
;
8 import com
.google
.apphosting
.api
.search
.DocumentPb
;
9 import com
.google
.common
.base
.Strings
;
11 import org
.antlr
.runtime
.RecognitionException
;
13 import java
.nio
.charset
.Charset
;
14 import java
.util
.Date
;
15 import java
.util
.Locale
;
18 * Provides checks for Field names, language code, and values: text, HTML, atom
23 public final class FieldChecker
{
26 * Checks whether a field name is valid. The field name length must be
27 * between 1 and {@link #MAXIMUM_NAME_LENGTH} and it should match
28 * {@link #FIELD_NAME_PATTERN}.
30 * @param name the field name to check
31 * @return the checked field name
32 * @throws IllegalArgumentException if the field name is null or empty
33 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
34 * match {@literal #FIELD_NAME_PATTERN}.
36 public static String
checkFieldName(String name
) {
37 return checkFieldName(name
, "field name");
41 * Checks whether a field name is valid. The field name length must be
42 * between 1 and {@link #MAXIMUM_NAME_LENGTH} and it should match
43 * {@link #FIELD_NAME_PATTERN}.
45 * @param name the field name to check
46 * @param fieldName the name of the Java field name of the class where
48 * @return the checked field name
49 * @throws IllegalArgumentException if the field name is null or empty
50 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
51 * match {@literal #FIELD_NAME_PATTERN}.
53 public static String
checkFieldName(String name
, String fieldName
) {
54 Preconditions
.checkArgument(name
!= null, "%s cannot be null", fieldName
);
55 Preconditions
.checkArgument(!Strings
.isNullOrEmpty(name
), "%s cannot be null or empty",
57 Preconditions
.checkArgument(bytesInString(name
) <= SearchApiLimits
.MAXIMUM_NAME_LENGTH
,
58 "%s longer than %d : %s", fieldName
, SearchApiLimits
.MAXIMUM_NAME_LENGTH
, name
);
59 Preconditions
.checkArgument(name
.matches(SearchApiLimits
.FIELD_NAME_PATTERN
),
60 "%s should match pattern %s: %s", fieldName
, SearchApiLimits
.FIELD_NAME_PATTERN
, name
);
65 * @return The number of bytes in the given string when UTF-8 encoded
67 static int bytesInString(String str
) {
68 return str
.getBytes(Charset
.forName("UTF-8")).length
;
72 * @return true if name matches {@link #FIELD_NAME_PATTERN}.
74 static boolean nameMatchesPattern(String name
) {
75 return name
.matches(SearchApiLimits
.FIELD_NAME_PATTERN
);
79 * Checks whether a text is valid. A text can be null, or a string between
80 * 0 and {@literal Field.MAXIMUM_TEXT_LENGTH} in length.
82 * @param text the text to check
83 * @return the checked text
84 * @throws IllegalArgumentException if text is too long
86 public static String
checkText(String text
) {
88 Preconditions
.checkArgument(bytesInString(text
) <= SearchApiLimits
.MAXIMUM_TEXT_LENGTH
,
89 "Field text longer than maximum length %d", SearchApiLimits
.MAXIMUM_TEXT_LENGTH
);
95 * Checks whether a html is valid. A html can be null or a string between
96 * 0 and {@literal Field.MAXIMUM_TEXT_LENGTH} in length.
98 * @param html the html to check
99 * @return the checked html
100 * @throws IllegalArgumentException if html is too long
102 public static String
checkHTML(String html
) {
104 Preconditions
.checkArgument(bytesInString(html
) <= SearchApiLimits
.MAXIMUM_TEXT_LENGTH
,
105 "html longer than maximum length %d", SearchApiLimits
.MAXIMUM_TEXT_LENGTH
);
111 * Checks whether an atom is valid. An atom can be null or a string between
112 * 1 and {@literal Field.MAXIMUM_ATOM_LENGTH} in length.
114 * @param atom the atom to check
115 * @return the checked atom
116 * @throws IllegalArgumentException if atom is too long
118 public static String
checkAtom(String atom
) {
120 Preconditions
.checkArgument(bytesInString(atom
) <= SearchApiLimits
.MAXIMUM_ATOM_LENGTH
,
121 "Field atom longer than maximum length %d", SearchApiLimits
.MAXIMUM_ATOM_LENGTH
);
127 * Checks whether a number is valid. A number can be null or a value between
128 * {@link #MIN_NUMBER_VALUE} and {@link #MAX_NUMBER_VALUE}.
130 * @param value the value to check
131 * @return the checked number
132 * @throws IllegalArgumentException if number is too long
134 public static Double
checkNumber(Double value
) {
136 Preconditions
.checkArgument(SearchApiLimits
.MINIMUM_NUMBER_VALUE
<= value
,
137 String
.format("number value, %f, must be greater than or equal to %f",
138 value
, SearchApiLimits
.MINIMUM_NUMBER_VALUE
));
139 Preconditions
.checkArgument(value
<= SearchApiLimits
.MAXIMUM_NUMBER_VALUE
,
140 String
.format("number value, %f, must be less than or equal to %f",
141 value
, SearchApiLimits
.MAXIMUM_NUMBER_VALUE
));
147 * Checks whether a date is within range. Date is nullable.
149 * @param date the date to check
150 * @return the checked date
151 * @throws IllegalArgumentException if date is out of range
153 public static Date
checkDate(Date date
) throws IllegalArgumentException
{
155 Preconditions
.checkArgument(
156 SearchApiLimits
.MINIMUM_DATE_VALUE
.compareTo(date
) <= 0,
157 String
.format("date %s must be after %s",
158 DateUtil
.formatDateTime(date
),
159 DateUtil
.formatDateTime(SearchApiLimits
.MINIMUM_DATE_VALUE
)));
160 Preconditions
.checkArgument(
161 date
.compareTo(SearchApiLimits
.MAXIMUM_DATE_VALUE
) <= 0,
162 String
.format("date %s must be before %s",
163 DateUtil
.formatDateTime(date
),
164 DateUtil
.formatDateTime(SearchApiLimits
.MAXIMUM_DATE_VALUE
)));
170 * Checks whether expression is not null and is parsable.
172 * @param expression the expression to check
173 * @return the checked expression
174 * @throws IllegalArgumentException if the expression is null, or
177 public static String
checkExpression(String expression
) {
178 Preconditions
.checkNotNull(expression
, "expression cannot be null");
180 new ExpressionTreeBuilder().parse(expression
);
181 } catch (RecognitionException e
) {
182 throw new IllegalArgumentException("Unable to parse expression: " + expression
);
187 public static DocumentPb
.Field
checkValid(DocumentPb
.Field field
) {
188 checkFieldName(field
.getName());
189 DocumentPb
.FieldValue value
= field
.getValue();
190 switch (value
.getType()) {
192 checkText(value
.getStringValue());
195 checkHTML(value
.getStringValue());
198 checkDate(DateUtil
.deserializeDate(value
.getStringValue()));
201 checkAtom(value
.getStringValue());
204 checkNumber(Double
.parseDouble(value
.getStringValue()));
207 GeoPointChecker
.checkValid(value
.getGeo());
210 throw new IllegalArgumentException("Unsupported field type " + value
.getType());
216 * Returns a {@link Locale} parsed from the given locale string.
218 * @param locale a string representation of a {@link Locale}
219 * @return a {@link Locale} parsed from the given locale string
220 * @throws IllegalArgumentException if the locale cannot be parsed
222 public static Locale
parseLocale(String locale
) {
223 if (locale
== null) {
226 String
[] parts
= locale
.split("_", 3);
227 if (parts
.length
== 1) {
228 return new Locale(parts
[0]);
230 if (parts
.length
== 2) {
231 return new Locale(parts
[0], parts
[1]);
233 if (parts
.length
== 3) {
234 return new Locale(parts
[0], parts
[1], parts
[2]);
236 throw new IllegalArgumentException("Cannot parse locale " + locale
);