Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / PropertyContainer.java
blobb826d06e905e9d3194ac6563aa83339f61e1fdde
1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com.google.appengine.api.datastore;
5 import com.google.appengine.api.datastore.Entity.UnindexedValue;
6 import com.google.appengine.api.datastore.Entity.WrappedValue;
7 import com.google.appengine.api.datastore.Entity.WrappedValueImpl;
9 import java.io.Serializable;
10 import java.util.ArrayList;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.Date;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.regex.Pattern;
18 /**
19 * A mutable property container.
22 public abstract class PropertyContainer implements Serializable, Cloneable {
23 static final Pattern RESERVED_NAME = Pattern.compile("^__.*__$");
25 PropertyContainer() {}
27 /**
28 * Gets the property with the specified name. The value returned
29 * may not be the same type as originally set via {@link #setProperty}.
31 * @return the property corresponding to {@code propertyName}.
33 public Object getProperty(String propertyName) {
34 return unwrapValue(getPropertyMap().get(propertyName));
37 /**
38 * Gets all of the properties belonging to this container.
40 * @return an unmodifiable {@code Map} of properties.
42 public Map<String, Object> getProperties() {
43 Map<String, Object> properties = new HashMap<String, Object>(getPropertyMap().size());
45 for (Map.Entry<String, Object> entry : getPropertyMap().entrySet()) {
46 properties.put(entry.getKey(), unwrapValue(entry.getValue()));
49 return Collections.unmodifiableMap(properties);
52 /**
53 * Returns true if a property has been set. This function can
54 * be used to test if a property has been specifically set
55 * to {@code null}.
57 * @return true iff the property named {@code propertyName} exists.
59 public boolean hasProperty(String propertyName) {
60 return getPropertyMap().containsKey(propertyName);
63 /**
64 * Removes any property with the specified name. If there is no
65 * property with this name set, simply does nothing.
67 * @throws NullPointerException If {@code propertyName} is null.
69 public void removeProperty(String propertyName) {
70 getPropertyMap().remove(propertyName);
73 /**
74 * Sets the property named, {@code propertyName}, to {@code value}.
75 * <p>
76 * As the value is stored in the datastore, it is converted to the
77 * datastore's native type. This may include widening, such as
78 * converting a {@code Short} to a {@code Long}.
79 * <p>
80 * If value is a {@code Collection}, the values will be stored in the
81 * datastore with the collection's iteration order with one caveat: all
82 * indexed values will come before all unindexed values (this can occur if the
83 * {@code Collection} contains both values that are normally indexed like
84 * strings, and values that are never indexed like {@link Blob}, {@link Text}
85 * and {@link EmbeddedEntity}).
86 * <p>
87 * Overrides any existing value for this property, whether indexed or
88 * unindexed.
89 * <p>
90 * Note that {@link Blob}, {@link Text} and {@link EmbeddedEntity} property
91 * values are never indexed by the built-in single property indexes. To store
92 * other types without being indexed, use {@link #setUnindexedProperty}.
94 * @param value may be one of the supported datatypes or a heterogeneous
95 * {@code Collection} of one of the supported datatypes.
97 * @throws IllegalArgumentException If the value is not of a type that
98 * the data store supports.
100 * @see #setUnindexedProperty
102 public void setProperty(String propertyName, Object value) {
103 DataTypeUtils.checkSupportedValue(propertyName, value);
104 getPropertyMap().put(propertyName, value);
108 * Like {@code #setProperty}, but doesn't index the property in the built-in
109 * single property indexes.
110 * <p>
111 * @param value may be one of the supported datatypes, or a heterogeneous
112 * {@code Collection} of one of the supported datatypes.
113 * <p>
114 * Overrides any existing value for this property, whether indexed or
115 * unindexed.
117 * @throws IllegalArgumentException If the value is not of a type that
118 * the data store supports.
120 * @see #setProperty
122 public void setUnindexedProperty(String propertyName, Object value) {
123 DataTypeUtils.checkSupportedValue(propertyName, value);
124 getPropertyMap().put(propertyName, new UnindexedValue(value));
128 * Returns true if {@code propertyName} has a value that will not be
129 * indexed. This includes {@link Text}, {@link Blob}, and any property
130 * added using {@link #setUnindexedProperty}.
132 * Note: The behavior of this method is not well defined in case of an indexed property whose
133 * value is a list that contains unindexable values.
135 public boolean isUnindexedProperty(String propertyName) {
136 Object value = getPropertyMap().get(propertyName);
137 return (value instanceof WrappedValue && !((WrappedValue) value).isIndexed())
138 || (value instanceof Text)
139 || (value instanceof Blob)
140 || (value instanceof EmbeddedEntity);
144 * A convenience method that populates properties from those in the given
145 * container.
147 * <p>This method transfers information about unindexed properties and clones
148 * any mutable values.
150 * @param src The container from which we will populate ourself.
152 public void setPropertiesFrom(PropertyContainer src) {
153 for (Map.Entry<String, Object> entry : src.getPropertyMap().entrySet()) {
154 String name = entry.getKey();
155 Object entryValue = entry.getValue();
157 boolean isUnindexedValue = false;
158 boolean isWrappedValue = false;
159 boolean indexed = true;
160 boolean forceIndexedEntityValue = false;
161 Object valueToAdd = entryValue;
163 if (entryValue instanceof UnindexedValue) {
164 isUnindexedValue = true;
165 indexed = false;
166 valueToAdd = ((UnindexedValue) entryValue).getValue();
167 } else if (entryValue instanceof WrappedValue) {
168 WrappedValue wrappedValue = (WrappedValue) entryValue;
169 isWrappedValue = true;
170 indexed = wrappedValue.isIndexed();
171 forceIndexedEntityValue = wrappedValue.getForceIndexedEmbeddedEntity();
172 valueToAdd = wrappedValue.getValue();
175 if (valueToAdd instanceof Collection<?>) {
176 Collection<?> srcColl = (Collection<?>) valueToAdd;
177 Collection<Object> destColl = new ArrayList<Object>(srcColl.size());
178 valueToAdd = destColl;
179 for (Object element : srcColl) {
180 destColl.add(cloneIfMutable(element));
182 } else {
183 valueToAdd = cloneIfMutable(valueToAdd);
186 if (isUnindexedValue) {
187 valueToAdd = new UnindexedValue(valueToAdd);
188 } else if (isWrappedValue) {
189 valueToAdd = new WrappedValueImpl(valueToAdd, indexed, forceIndexedEntityValue);
192 getPropertyMap().put(name, valueToAdd);
196 abstract Map<String, Object> getPropertyMap();
199 * If obj is an {@code UnindexedValue}, returns the value it wraps.
200 * Otherwise, returns {@code obj}.
202 * @param obj may be null
204 static Object unwrapValue(Object obj) {
205 if (obj instanceof WrappedValue) {
206 return ((WrappedValue) obj).getValue();
207 } else {
208 return obj;
212 @Override
213 protected Object clone() {
214 throw new UnsupportedOperationException();
218 * Returns a clone of the provided object if it is mutable, otherwise
219 * just return the provided object.
221 private static Object cloneIfMutable(Object obj) {
222 if (obj instanceof Date) {
223 return ((Date) obj).clone();
225 else if (obj instanceof PropertyContainer) {
226 return ((PropertyContainer) obj).clone();
228 return obj;
231 protected void appendPropertiesTo(StringBuilder builder) {
232 for (Map.Entry<String, Object> entry : getPropertyMap().entrySet()) {
233 builder.append('\t')
234 .append(entry.getKey())
235 .append(" = ")
236 .append(entry.getValue())
237 .append('\n');