App Engine Java SDK version 1.9.25
[gae.git] / java / src / main / com / google / appengine / api / datastore / DataTypeUtils.java
blob7c6a8e90b0df6946e641dee336815d35a00eb8bf
1 // Copyright 2009 Google Inc. All rights reserved.
3 package com.google.appengine.api.datastore;
5 import static com.google.appengine.api.datastore.DataTypeUtils.CheckValueOption.ALLOW_MULTI_VALUE;
6 import static com.google.appengine.api.datastore.DataTypeUtils.CheckValueOption.REQUIRE_INDEXABLE;
7 import static com.google.appengine.api.datastore.DataTypeUtils.CheckValueOption.REQUIRE_MULTI_VALUE;
8 import static com.google.appengine.api.datastore.DataTypeUtils.CheckValueOption.VALUE_PRE_CHECKED_WITHOUT_NAME;
9 import static java.nio.charset.StandardCharsets.UTF_8;
11 import com.google.appengine.api.blobstore.BlobKey;
12 import com.google.appengine.api.users.User;
13 import com.google.common.collect.ImmutableSet;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.Date;
18 import java.util.EnumSet;
19 import java.util.HashSet;
20 import java.util.Set;
21 import java.util.logging.Logger;
23 /**
24 * {@code DataTypeUtils} presents a simpler interface that allows
25 * user-code to determine what Classes can safely be stored as
26 * properties in the data store.
28 * Currently this list includes:
29 * <ul>
30 * <li>{@link String} (but not {@link StringBuffer}),
32 * <li>All numeric primitive wrappers ({@link Byte} through {@link
33 * Long}, {@link Float} and {@link Double}, but not {@link
34 * java.math.BigInteger} or {@link java.math.BigDecimal}.
36 * <li>{@link Key}, for storing references to other {@link Entity}
37 * objects.
39 * <li>{@link User}, for storing references to users.
41 * <li>{@link ShortBlob}, for storing binary data small enough to be indexed.
42 * This means properties of this type, unlike {@link Blob} properties, can be
43 * filtered and sorted on in queries.
45 * <li>{@link Blob}, for storing unindexed binary data less than 1MB.
47 * <li>{@link Text}, for storing unindexed String data less than 1MB.
49 * <li>{@link BlobKey}, for storing references to user uploaded
50 * blobs (which may exceed 1MB).
52 * <li>{@link Date}.
54 * <li>{@link Link}.
55 * </ul>
58 public final class DataTypeUtils {
60 /**
61 * Options for checking if a property value is valid for a property.
63 enum CheckValueOption {
65 /**
66 * Allow the value to be a collection of values.
68 ALLOW_MULTI_VALUE,
70 /**
71 * Require the value to be a collection of values.
73 REQUIRE_MULTI_VALUE,
75 /**
76 * The value's validity has already been checked but not in conjunction with the property
77 * name.
79 VALUE_PRE_CHECKED_WITHOUT_NAME,
81 /**
82 * Require the value to be indexable or a collection of indexable values.
84 REQUIRE_INDEXABLE,
87 private static final Logger logger = Logger.getLogger(DataTypeUtils.class.getName());
89 /**
90 * This is the maximum number of bytes that a string property
91 * can contain. If your string has more bytes, you need to
92 * wrap it in a {@link Text}.
94 public static final int MAX_STRING_PROPERTY_LENGTH = 1500;
96 /**
97 * This is the maximum number of bytes that a {@code ShortBlob} property
98 * can contain. If your data is larger, you need to use a {@code Blob}.
100 public static final int MAX_SHORT_BLOB_PROPERTY_LENGTH = 1500;
102 public static final int MAX_LINK_PROPERTY_LENGTH = 2083;
104 private static final Set<Class<?>> SUPPORTED_TYPES = new HashSet<Class<?>>();
105 static {
106 Collections.addAll(SUPPORTED_TYPES,
107 RawValue.class,
108 Boolean.class,
109 String.class,
110 Byte.class,
111 Short.class,
112 Integer.class,
113 Long.class,
114 Float.class,
115 Double.class,
116 User.class,
117 Key.class,
118 Blob.class,
119 Text.class,
120 Date.class,
121 Link.class,
122 ShortBlob.class,
123 GeoPt.class,
124 Category.class,
125 Rating.class,
126 PhoneNumber.class,
127 PostalAddress.class,
128 Email.class,
129 IMHandle.class,
130 BlobKey.class,
131 EmbeddedEntity.class);
134 private static final ImmutableSet<Class<?>> UNINDEXABLE_TYPES =
135 ImmutableSet.<Class<?>>of(Blob.class, Text.class);
138 * If the specified object cannot be used as the value for a {@code
139 * Entity} property, throw an exception with the appropriate
140 * explanation.
142 * @throws NullPointerException if the specified value is null
143 * @throws IllegalArgumentException if the type is not supported, or
144 * if the object is in some other way invalid.
146 public static void checkSupportedValue(Object value) {
147 checkSupportedValue(null, value);
151 * If the specified object cannot be used as the value for a {@code
152 * Entity} property, throw an exception with the appropriate
153 * explanation.
155 * @throws NullPointerException if the specified value is null
156 * @throws IllegalArgumentException if the type is not supported, or
157 * if the object is in some other way invalid.
159 public static void checkSupportedValue(String name, Object value) {
160 checkSupportedValue(name, value, true, false, false);
164 * If the specified object cannot be used as the value for a {@code
165 * Entity} property, throw an exception with the appropriate
166 * explanation.
168 * @param name name of the property
169 * @param value value in question
170 * @param allowMultiValue if this property allows multivalue values
171 * @param requireMultiValue if this property requires multivalue values
172 * @param requireIndexable if this property is required to be indexable
174 * @throws IllegalArgumentException if the type is not supported, or
175 * if the object is in some other way invalid.
177 static void checkSupportedValue(String name, Object value,
178 boolean allowMultiValue, boolean requireMultiValue, boolean requireIndexable) {
179 EnumSet<CheckValueOption> options = EnumSet.noneOf(CheckValueOption.class);
180 if (allowMultiValue) {
181 options.add(ALLOW_MULTI_VALUE);
183 if (requireMultiValue) {
184 options.add(REQUIRE_MULTI_VALUE);
186 if (requireIndexable) {
187 options.add(REQUIRE_INDEXABLE);
189 checkSupportedValue(name, value, options, SUPPORTED_TYPES);
193 * If the specified object cannot be used as the value for a {@code
194 * Entity} property, throw an exception with the appropriate
195 * explanation.
197 * @param name name of the property
198 * @param value value in question
199 * @param options the options for this check invocation.
200 * @param supportedTypes the types considered to be valid types for the value.
202 * @throws IllegalArgumentException if the type is not supported, or
203 * if the object is in some other way invalid.
205 static void checkSupportedValue(String name, Object value, EnumSet<CheckValueOption> options,
206 Set<Class<?>> supportedTypes) {
207 if (value instanceof Collection<?>) {
208 if (!options.contains(ALLOW_MULTI_VALUE)) {
209 throw new IllegalArgumentException("A collection of values is not allowed.");
212 Collection<?> values = (Collection<?>) value;
213 if (!values.isEmpty()) {
214 for (Object obj : values) {
215 checkSupportedSingleValue(name, obj, options, supportedTypes);
217 } else if (options.contains(REQUIRE_MULTI_VALUE)) {
218 throw new IllegalArgumentException("A collection with at least one value is required.");
220 } else if (options.contains(REQUIRE_MULTI_VALUE)) {
221 throw new IllegalArgumentException("A collection of values is required.");
222 } else {
223 checkSupportedSingleValue(name, value, options, supportedTypes);
227 private static void checkSupportedSingleValue(String name, Object value,
228 EnumSet<CheckValueOption> options, Set<Class<?>> supportedTypes) {
229 if (value == null) {
230 return;
233 if (Entity.KEY_RESERVED_PROPERTY.equals(name)) {
234 if (!(value instanceof Key)) {
235 logger.warning(Entity.KEY_RESERVED_PROPERTY + " value should be of type Key");
239 if (options.contains(VALUE_PRE_CHECKED_WITHOUT_NAME)) {
240 return;
243 String prefix;
244 if (name == null) {
245 prefix = "";
246 } else {
247 prefix = name + ": ";
250 if (!supportedTypes.contains(value.getClass())) {
251 throw new IllegalArgumentException(
252 prefix + value.getClass().getName() + " is not a supported property type.");
255 if (options.contains(REQUIRE_INDEXABLE) && isUnindexableType(value.getClass())) {
256 throw new IllegalArgumentException(
257 prefix + value.getClass().getName() + " is not indexable.");
260 if (value instanceof String) {
261 int length = ((String) value).getBytes(UTF_8).length;
262 if (length > MAX_STRING_PROPERTY_LENGTH) {
263 throw new IllegalArgumentException(
264 prefix + "String properties must be " + MAX_STRING_PROPERTY_LENGTH
265 + " bytes or less. Instead, use " + Text.class.getName() + ", which can store "
266 + "strings of any length.");
268 } else if (value instanceof Link) {
269 int length = ((Link) value).getValue().getBytes(UTF_8).length;
270 if (length > MAX_LINK_PROPERTY_LENGTH) {
271 throw new IllegalArgumentException(
272 prefix + "Link properties must be " + MAX_LINK_PROPERTY_LENGTH
273 + " bytes or less. Instead, use " + Text.class.getName() + ", which can store "
274 + "strings of any length.");
276 } else if (value instanceof ShortBlob) {
277 int length = ((ShortBlob) value).getBytes().length;
278 if (length > MAX_SHORT_BLOB_PROPERTY_LENGTH) {
279 throw new IllegalArgumentException(prefix + "byte[] properties must be "
280 + MAX_SHORT_BLOB_PROPERTY_LENGTH
281 + " bytes or less. Instead, use " + Blob.class.getName() + ", which can store binary "
282 + "data of any size.");
288 * Returns true if and only if the supplied {@code Class} can be
289 * stored in the data store.
291 public static boolean isSupportedType(Class<?> clazz) {
292 return SUPPORTED_TYPES.contains(clazz);
296 * Returns an unmodifiable {@code Set} of supported {@code Class}
297 * objects.
299 public static Set<Class<?>> getSupportedTypes() {
300 return Collections.unmodifiableSet(SUPPORTED_TYPES);
304 * Returns true if the supplied {@code Class} cannot be indexed.
306 public static boolean isUnindexableType(Class<?> clazz) {
307 return UNINDEXABLE_TYPES.contains(clazz);
310 private DataTypeUtils() {