Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / search / checkers / FieldChecker.java
blobf59a83aec31ed275fa3df81d7f8a703c78714975
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.appengine.api.search.Util;
8 import com.google.apphosting.api.search.DocumentPb;
10 import org.antlr.runtime.RecognitionException;
12 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 public final class FieldChecker {
24 /**
25 * The maximum length of a field name in bytes ({@value}).
27 public static final int MAXIMUM_NAME_LENGTH = 500;
29 /**
30 * The maximum length of a text or HTML in bytes ({@value}).
32 public static final int MAXIMUM_TEXT_LENGTH = 1024 * 1024;
34 /**
35 * The maximum length of an atom in bytes ({@value}).
37 public static final int MAXIMUM_ATOM_LENGTH = 500;
39 /**
40 * The maximum value that can be stored in a number field ({@value}).
42 public static final float MAX_NUMBER_VALUE = 2147483647;
44 /**
45 * The minimum value that can be stored in a number field ({@value}).
47 public static final float MIN_NUMBER_VALUE = -2147483647;
49 /**
50 * The pattern each document field name should match.
52 public static final String FIELD_NAME_PATTERN = "^[A-Za-z][A-Za-z0-9_]*$";
54 /**
55 * Checks whether a field name is valid. The field name length must be
56 * between 1 and {@link #MAXIMUM_NAME_LENGTH} and it should match
57 * {@link #FIELD_NAME_PATTERN}.
59 * @param name the field name to check
60 * @return the checked field name
61 * @throws IllegalArgumentException if the field name is null or empty
62 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
63 * match {@literal #FIELD_NAME_PATTERN}.
65 public static String checkFieldName(String name) {
66 return checkFieldName(name, "field name");
69 /**
70 * Checks whether a field name is valid. The field name length must be
71 * between 1 and {@link #MAXIMUM_NAME_LENGTH} and it should match
72 * {@link #FIELD_NAME_PATTERN}.
74 * @param name the field name to check
75 * @param fieldName the name of the Java field name of the class where
76 * name is checked
77 * @return the checked field name
78 * @throws IllegalArgumentException if the field name is null or empty
79 * or is longer than {@literal Field.MAXIMUM_NAME_LENGTH} or it doesn't
80 * match {@literal #FIELD_NAME_PATTERN}.
82 public static String checkFieldName(String name, String fieldName) {
83 Preconditions.checkArgument(name != null, "%s cannot be null", fieldName);
84 Preconditions.checkArgument(!Util.isNullOrEmpty(name), "%s cannot be null or empty",
85 fieldName);
86 Preconditions.checkArgument(bytesInString(name) <= MAXIMUM_NAME_LENGTH,
87 "%s longer than %d : %s", fieldName, MAXIMUM_NAME_LENGTH, name);
88 Preconditions.checkArgument(name.matches(FIELD_NAME_PATTERN),
89 "%s should match pattern %s: %s", fieldName, FIELD_NAME_PATTERN, name);
90 return name;
93 /**
94 * @return The number of bytes in the given string when UTF-8 encoded
96 static int bytesInString(String str) {
97 return str.getBytes(Charset.forName("UTF-8")).length;
101 * @return true if name matches {@link #FIELD_NAME_PATTERN}.
103 static boolean nameMatchesPattern(String name) {
104 return name.matches(FIELD_NAME_PATTERN);
108 * Checks whether a text is valid. A text can be null, or a string between
109 * 0 and {@literal Field.MAXIMUM_TEXT_LENGTH} in length.
111 * @param text the text to check
112 * @return the checked text
113 * @throws IllegalArgumentException if text is too long
115 public static String checkText(String text) {
116 if (text != null) {
117 Preconditions.checkArgument(bytesInString(text) <= MAXIMUM_TEXT_LENGTH,
118 "Field text longer than maximum length %d", MAXIMUM_TEXT_LENGTH);
120 return text;
124 * Checks whether a html is valid. A html can be null or a string between
125 * 0 and {@literal Field.MAXIMUM_TEXT_LENGTH} in length.
127 * @param html the html to check
128 * @return the checked html
129 * @throws IllegalArgumentException if html is too long
131 public static String checkHTML(String html) {
132 if (html != null) {
133 Preconditions.checkArgument(bytesInString(html) <= MAXIMUM_TEXT_LENGTH,
134 "html longer than maximum length %d", MAXIMUM_TEXT_LENGTH);
136 return html;
140 * Checks whether an atom is valid. An atom can be null or a string between
141 * 1 and {@literal Field.MAXIMUM_ATOM_LENGTH} in length.
143 * @param atom the atom to check
144 * @return the checked atom
145 * @throws IllegalArgumentException if atom is too long
147 public static String checkAtom(String atom) {
148 if (atom != null) {
149 Preconditions.checkArgument(bytesInString(atom) <= MAXIMUM_ATOM_LENGTH,
150 "Field atom longer than maximum length %d", MAXIMUM_ATOM_LENGTH);
152 return atom;
156 * Checks whether a number is valid. A number can be null or a value between
157 * {@link #MIN_NUMBER_VALUE} and {@link #MAX_NUMBER_VALUE}.
159 * @param value the value to check
160 * @return the checked number
161 * @throws IllegalArgumentException if number is too long
163 public static Double checkNumber(Double value) {
164 if (value != null) {
165 Preconditions.checkArgument(MIN_NUMBER_VALUE <= value,
166 String.format("number value, %f, must be greater than or equal to %f",
167 value, MIN_NUMBER_VALUE));
168 Preconditions.checkArgument(value <= MAX_NUMBER_VALUE,
169 String.format("number value, %f, must be less than or equal to %f",
170 value, MAX_NUMBER_VALUE));
172 return value;
176 * Checks whether a date is within range. Date is nullable.
178 * @param date the date to check
179 * @return the checked date
180 * @throws IllegalArgumentException if date is out of range
182 public static Date checkDate(Date date) throws IllegalArgumentException {
183 if (date != null) {
184 Preconditions.checkArgument(
185 DateUtil.MIN_DATE.compareTo(date) <= 0,
186 String.format("date %s must be after %s",
187 DateUtil.formatDateTime(date), DateUtil.formatDateTime(DateUtil.MIN_DATE)));
188 Preconditions.checkArgument(
189 date.compareTo(DateUtil.MAX_DATE) <= 0,
190 String.format("date %s must be before %s",
191 DateUtil.formatDateTime(date), DateUtil.formatDateTime(DateUtil.MAX_DATE)));
193 return date;
197 * Checks whether expression is not null and is parsable.
199 * @param expression the expression to check
200 * @return the checked expression
201 * @throws IllegalArgumentException if the expression is null, or
202 * cannot be parsed
204 public static String checkExpression(String expression) {
205 Preconditions.checkNotNull(expression, "expression cannot be null");
206 try {
207 new ExpressionTreeBuilder().parse(expression);
208 } catch (RecognitionException e) {
209 throw new IllegalArgumentException("Unable to parse expression: " + expression);
211 return expression;
214 public static DocumentPb.Field checkValid(DocumentPb.Field field) {
215 checkFieldName(field.getName());
216 DocumentPb.FieldValue value = field.getValue();
217 switch (value.getType()) {
218 case TEXT:
219 checkText(value.getStringValue());
220 break;
221 case HTML:
222 checkHTML(value.getStringValue());
223 break;
224 case DATE:
225 checkDate(DateUtil.deserializeDate(value.getStringValue()));
226 break;
227 case ATOM:
228 checkAtom(value.getStringValue());
229 break;
230 case NUMBER:
231 checkNumber(Double.parseDouble(value.getStringValue()));
232 break;
233 case GEO:
234 GeoPointChecker.checkValid(value.getGeo());
235 break;
236 default:
237 throw new IllegalArgumentException("Unsupported field type " + value.getType());
239 return field;
243 * Returns a {@link Locale} parsed from the given locale string.
245 * @param locale a string representation of a {@link Locale}
246 * @return a {@link Locale} parsed from the given locale string
247 * @throws IllegalArgumentException if the locale cannot be parsed
249 public static Locale parseLocale(String locale) {
250 if (locale == null) {
251 return null;
253 String[] parts = locale.split("_", 3);
254 if (parts.length == 1) {
255 return new Locale(parts[0]);
257 if (parts.length == 2) {
258 return new Locale(parts[0], parts[1]);
260 if (parts.length == 3) {
261 return new Locale(parts[0], parts[1], parts[2]);
263 throw new IllegalArgumentException("Cannot parse locale " + locale);