1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / api / search / checkers / FieldChecker.java
blobbc862c2d944f3b7b3c72f260640280663039c3bc
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;
16 /**
17 * Provides checks for Field names, language code, and values: text, HTML, atom
18 * or date.
21 public final class FieldChecker {
23 /**
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");
38 /**
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
45 * name is checked
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",
53 fieldName);
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);
58 return name;
61 /**
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;
68 /**
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);
75 /**
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) {
84 if (text != null) {
85 Preconditions.checkArgument(bytesInString(text) <= SearchApiLimits.MAXIMUM_TEXT_LENGTH,
86 "Field text longer than maximum length %d", SearchApiLimits.MAXIMUM_TEXT_LENGTH);
88 return text;
91 /**
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) {
100 if (html != null) {
101 Preconditions.checkArgument(bytesInString(html) <= SearchApiLimits.MAXIMUM_TEXT_LENGTH,
102 "html longer than maximum length %d", SearchApiLimits.MAXIMUM_TEXT_LENGTH);
104 return html;
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) {
116 if (atom != null) {
117 Preconditions.checkArgument(bytesInString(atom) <= SearchApiLimits.MAXIMUM_ATOM_LENGTH,
118 "Field atom longer than maximum length %d", SearchApiLimits.MAXIMUM_ATOM_LENGTH);
120 return atom;
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);
136 return prefix;
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},
142 * inclusive.
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) {
149 if (value != null) {
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));
157 return 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 {
168 if (date != null) {
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)));
180 return date;
183 private static String checkExpressionHelper(String expression, String mode) {
184 Preconditions.checkNotNull(expression, "expression cannot be null");
185 ExpressionTreeBuilder parser = new ExpressionTreeBuilder();
186 try {
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);
194 return expression;
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
203 * cannot be parsed
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
215 * cannot be parsed
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()) {
225 case TEXT:
226 checkText(value.getStringValue());
227 break;
228 case HTML:
229 checkHTML(value.getStringValue());
230 break;
231 case DATE:
232 checkDate(DateUtil.deserializeDate(value.getStringValue()));
233 break;
234 case ATOM:
235 checkAtom(value.getStringValue());
236 break;
237 case NUMBER:
238 checkNumber(Double.parseDouble(value.getStringValue()));
239 break;
240 case GEO:
241 GeoPointChecker.checkValid(value.getGeo());
242 break;
243 case PREFIX:
244 checkPrefix(value.getStringValue());
245 break;
246 default:
247 throw new IllegalArgumentException("Unsupported field type " + value.getType());
249 return field;
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) {
261 return 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);