Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / search / checkers / FieldChecker.java
blob2f17176b1bc79f9e2b80d96c53996d6f4579e009
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;
17 /**
18 * Provides checks for Field names, language code, and values: text, HTML, atom
19 * or date.
22 @AppEngineInternal
23 public final class FieldChecker {
25 /**
26 * The maximum length of a field name in bytes ({@value}).
27 * @deprecated From 1.7.4, use {@link SearchApiLimits#MAXIMUM_NAME_LENGTH}
29 @Deprecated public static final int MAXIMUM_NAME_LENGTH = 500;
31 /**
32 * The maximum length of a text or HTML in bytes ({@value}).
33 * @deprecated From 1.7.4, use {@link SearchApiLimits#MAXIMUM_TEXT_LENGTH}
35 @Deprecated public static final int MAXIMUM_TEXT_LENGTH = 1024 * 1024;
37 /**
38 * The maximum length of an atom in bytes ({@value}).
39 * @deprecated From 1.7.4, use {@link SearchApiLimits#MAXIMUM_ATOM_LENGTH}
41 @Deprecated public static final int MAXIMUM_ATOM_LENGTH = 500;
43 /**
44 * The maximum value that can be stored in a number field ({@value}).
45 * @deprecated From 1.7.4, use {@link SearchApiLimits#MAXIMUM_NUMBER_VALUE}
47 @Deprecated public static final float MAX_NUMBER_VALUE = 2147483647;
49 /**
50 * The minimum value that can be stored in a number field ({@value}).
51 * @deprecated From 1.7.4, use {@link SearchApiLimits#MINIMUM_NUMBER_VALUE}
53 @Deprecated public static final float MIN_NUMBER_VALUE = -2147483647;
55 /**
56 * The pattern each document field name should match.
57 * @deprecated From 1.7.4, use {@link SearchApiLimits#FIELD_NAME_PATTERN}
59 @Deprecated public static final String FIELD_NAME_PATTERN = "^[A-Za-z][A-Za-z0-9_]*$";
61 /**
62 * The maximum date that can be stored in a date field.
63 * @deprecated From 1.7.4, use {@link SearchApiLimits#MAXIMUM_DATE_VALUE}
65 @Deprecated public static final Date MAX_DATE =
66 DateUtil.getEpochPlusDays(
67 Integer.MAX_VALUE, DateUtil.MILLISECONDS_IN_DAY - 1);
69 /**
70 * The minimum date that can be stored in a date field.
71 * @deprecated From 1.7.4, use {@link SearchApiLimits#MINIMUM_DATE_VALUE}
73 @Deprecated public static final Date MIN_DATE =
74 DateUtil.getEpochPlusDays(Integer.MIN_VALUE, 0);
76 /**
77 * Checks whether a field name is valid. The field name length must be
78 * between 1 and {@link #MAXIMUM_NAME_LENGTH} and it should match
79 * {@link #FIELD_NAME_PATTERN}.
81 * @param name the field name to check
82 * @return the checked field name
83 * @throws IllegalArgumentException if the field name is null or empty
84 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
85 * match {@literal #FIELD_NAME_PATTERN}.
87 public static String checkFieldName(String name) {
88 return checkFieldName(name, "field name");
91 /**
92 * Checks whether a field name is valid. The field name length must be
93 * between 1 and {@link #MAXIMUM_NAME_LENGTH} and it should match
94 * {@link #FIELD_NAME_PATTERN}.
96 * @param name the field name to check
97 * @param fieldName the name of the Java field name of the class where
98 * name is checked
99 * @return the checked field name
100 * @throws IllegalArgumentException if the field name is null or empty
101 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
102 * match {@literal #FIELD_NAME_PATTERN}.
104 public static String checkFieldName(String name, String fieldName) {
105 Preconditions.checkArgument(name != null, "%s cannot be null", fieldName);
106 Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "%s cannot be null or empty",
107 fieldName);
108 Preconditions.checkArgument(bytesInString(name) <= SearchApiLimits.MAXIMUM_NAME_LENGTH,
109 "%s longer than %d : %s", fieldName, SearchApiLimits.MAXIMUM_NAME_LENGTH, name);
110 Preconditions.checkArgument(name.matches(SearchApiLimits.FIELD_NAME_PATTERN),
111 "%s should match pattern %s: %s", fieldName, SearchApiLimits.FIELD_NAME_PATTERN, name);
112 return name;
116 * @return The number of bytes in the given string when UTF-8 encoded
118 static int bytesInString(String str) {
119 return str.getBytes(Charset.forName("UTF-8")).length;
123 * @return true if name matches {@link #FIELD_NAME_PATTERN}.
125 static boolean nameMatchesPattern(String name) {
126 return name.matches(SearchApiLimits.FIELD_NAME_PATTERN);
130 * Checks whether a text is valid. A text can be null, or a string between
131 * 0 and {@literal Field.MAXIMUM_TEXT_LENGTH} in length.
133 * @param text the text to check
134 * @return the checked text
135 * @throws IllegalArgumentException if text is too long
137 public static String checkText(String text) {
138 if (text != null) {
139 Preconditions.checkArgument(bytesInString(text) <= SearchApiLimits.MAXIMUM_TEXT_LENGTH,
140 "Field text longer than maximum length %d", SearchApiLimits.MAXIMUM_TEXT_LENGTH);
142 return text;
146 * Checks whether a html is valid. A html can be null or a string between
147 * 0 and {@literal Field.MAXIMUM_TEXT_LENGTH} in length.
149 * @param html the html to check
150 * @return the checked html
151 * @throws IllegalArgumentException if html is too long
153 public static String checkHTML(String html) {
154 if (html != null) {
155 Preconditions.checkArgument(bytesInString(html) <= SearchApiLimits.MAXIMUM_TEXT_LENGTH,
156 "html longer than maximum length %d", SearchApiLimits.MAXIMUM_TEXT_LENGTH);
158 return html;
162 * Checks whether an atom is valid. An atom can be null or a string between
163 * 1 and {@literal Field.MAXIMUM_ATOM_LENGTH} in length.
165 * @param atom the atom to check
166 * @return the checked atom
167 * @throws IllegalArgumentException if atom is too long
169 public static String checkAtom(String atom) {
170 if (atom != null) {
171 Preconditions.checkArgument(bytesInString(atom) <= SearchApiLimits.MAXIMUM_ATOM_LENGTH,
172 "Field atom longer than maximum length %d", SearchApiLimits.MAXIMUM_ATOM_LENGTH);
174 return atom;
178 * Checks whether a number is valid. A number can be null or a value between
179 * {@link #MIN_NUMBER_VALUE} and {@link #MAX_NUMBER_VALUE}.
181 * @param value the value to check
182 * @return the checked number
183 * @throws IllegalArgumentException if number is too long
185 public static Double checkNumber(Double value) {
186 if (value != null) {
187 Preconditions.checkArgument(SearchApiLimits.MINIMUM_NUMBER_VALUE <= value,
188 String.format("number value, %f, must be greater than or equal to %f",
189 value, SearchApiLimits.MINIMUM_NUMBER_VALUE));
190 Preconditions.checkArgument(value <= SearchApiLimits.MAXIMUM_NUMBER_VALUE,
191 String.format("number value, %f, must be less than or equal to %f",
192 value, SearchApiLimits.MAXIMUM_NUMBER_VALUE));
194 return value;
198 * Checks whether a date is within range. Date is nullable.
200 * @param date the date to check
201 * @return the checked date
202 * @throws IllegalArgumentException if date is out of range
204 public static Date checkDate(Date date) throws IllegalArgumentException {
205 if (date != null) {
206 Preconditions.checkArgument(
207 SearchApiLimits.MINIMUM_DATE_VALUE.compareTo(date) <= 0,
208 String.format("date %s must be after %s",
209 DateUtil.formatDateTime(date),
210 DateUtil.formatDateTime(SearchApiLimits.MINIMUM_DATE_VALUE)));
211 Preconditions.checkArgument(
212 date.compareTo(SearchApiLimits.MAXIMUM_DATE_VALUE) <= 0,
213 String.format("date %s must be before %s",
214 DateUtil.formatDateTime(date),
215 DateUtil.formatDateTime(SearchApiLimits.MAXIMUM_DATE_VALUE)));
217 return date;
221 * Checks whether expression is not null and is parsable.
223 * @param expression the expression to check
224 * @return the checked expression
225 * @throws IllegalArgumentException if the expression is null, or
226 * cannot be parsed
228 public static String checkExpression(String expression) {
229 Preconditions.checkNotNull(expression, "expression cannot be null");
230 try {
231 new ExpressionTreeBuilder().parse(expression);
232 } catch (RecognitionException e) {
233 throw new IllegalArgumentException("Unable to parse expression: " + expression);
235 return expression;
238 public static DocumentPb.Field checkValid(DocumentPb.Field field) {
239 checkFieldName(field.getName());
240 DocumentPb.FieldValue value = field.getValue();
241 switch (value.getType()) {
242 case TEXT:
243 checkText(value.getStringValue());
244 break;
245 case HTML:
246 checkHTML(value.getStringValue());
247 break;
248 case DATE:
249 checkDate(DateUtil.deserializeDate(value.getStringValue()));
250 break;
251 case ATOM:
252 checkAtom(value.getStringValue());
253 break;
254 case NUMBER:
255 checkNumber(Double.parseDouble(value.getStringValue()));
256 break;
257 case GEO:
258 GeoPointChecker.checkValid(value.getGeo());
259 break;
260 default:
261 throw new IllegalArgumentException("Unsupported field type " + value.getType());
263 return field;
267 * Returns a {@link Locale} parsed from the given locale string.
269 * @param locale a string representation of a {@link Locale}
270 * @return a {@link Locale} parsed from the given locale string
271 * @throws IllegalArgumentException if the locale cannot be parsed
273 public static Locale parseLocale(String locale) {
274 if (locale == null) {
275 return null;
277 String[] parts = locale.split("_", 3);
278 if (parts.length == 1) {
279 return new Locale(parts[0]);
281 if (parts.length == 2) {
282 return new Locale(parts[0], parts[1]);
284 if (parts.length == 3) {
285 return new Locale(parts[0], parts[1], parts[2]);
287 throw new IllegalArgumentException("Cannot parse locale " + locale);