Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / PropertyContainer.java
blobe7bdf7610c886d4408d29cd61b51fdee321af5b8
1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com.google.appengine.api.datastore;
5 import com.google.appengine.api.datastore.Entity.UnindexedValue;
7 import java.io.Serializable;
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.Collections;
11 import java.util.Date;
12 import java.util.HashMap;
13 import java.util.Map;
14 import java.util.regex.Pattern;
16 /**
17 * A mutable property container.
20 public abstract class PropertyContainer implements Serializable, Cloneable {
21 static final Pattern RESERVED_NAME = Pattern.compile("^__.*__$");
23 PropertyContainer() {}
25 /**
26 * Gets the property with the specified name. The value returned
27 * may not be the same type as originally set via {@link #setProperty}.
29 * @return the property corresponding to {@code propertyName}.
31 public Object getProperty(String propertyName) {
32 return unwrapValue(getPropertyMap().get(propertyName));
35 /**
36 * Gets all of the properties belonging to this container.
38 * @return an unmodifiable {@code Map} of properties.
40 public Map<String, Object> getProperties() {
41 Map<String, Object> properties = new HashMap<String, Object>(getPropertyMap().size());
43 for (Map.Entry<String, Object> entry : getPropertyMap().entrySet()) {
44 properties.put(entry.getKey(), unwrapValue(entry.getValue()));
47 return Collections.unmodifiableMap(properties);
50 /**
51 * Returns true if a property has been set. This function can
52 * be used to test if a property has been specifically set
53 * to {@code null}.
55 * @return true iff the property named {@code propertyName} exists.
57 public boolean hasProperty(String propertyName) {
58 return getPropertyMap().containsKey(propertyName);
61 /**
62 * Removes any property with the specified name. If there is no
63 * property with this name set, simply does nothing.
65 * @throws NullPointerException If {@code propertyName} is null.
67 public void removeProperty(String propertyName) {
68 getPropertyMap().remove(propertyName);
71 /**
72 * Sets the property named, {@code propertyName}, to {@code value}.
73 * <p>
74 * As the value is stored in the datastore, it is converted to the
75 * datastore's native type. This may include widening, such as
76 * converting a {@code Short} to a {@code Long}.
77 * <p>
78 * If value is a {@code Collection}, the values will be stored in the
79 * datastore with the collection's iteration order with one caveat: all
80 * indexed values will come before all unindexed values (this can occur if the
81 * {@code Collection} contains both values that are normally indexed like
82 * strings, and values that are never indexed like {@link Blob}, {@link Text}
83 * and {@link EmbeddedEntity}).
84 * <p>
85 * Overrides any existing value for this property, whether indexed or
86 * unindexed.
87 * <p>
88 * Note that {@link Blob}, {@link Text} and {@link EmbeddedEntity} property
89 * values are never indexed by the built-in single property indexes. To store
90 * other types without being indexed, use {@link #setUnindexedProperty}.
92 * @param value may be one of the supported datatypes or a heterogeneous
93 * {@code Collection} of one of the supported datatypes.
95 * @throws IllegalArgumentException If the value is not of a type that
96 * the data store supports.
98 * @see #setUnindexedProperty
100 public void setProperty(String propertyName, Object value) {
101 DataTypeUtils.checkSupportedValue(propertyName, value);
102 getPropertyMap().put(propertyName, value);
106 * Like {@code #setProperty}, but doesn't index the property in the built-in
107 * single property indexes.
108 * <p>
109 * @param value may be one of the supported datatypes, or a heterogeneous
110 * {@code Collection} of one of the supported datatypes.
111 * <p>
112 * Overrides any existing value for this property, whether indexed or
113 * unindexed.
115 * @throws IllegalArgumentException If the value is not of a type that
116 * the data store supports.
118 * @see #setProperty
120 public void setUnindexedProperty(String propertyName, Object value) {
121 DataTypeUtils.checkSupportedValue(propertyName, value);
122 getPropertyMap().put(propertyName, new UnindexedValue(value));
126 * Returns true if {@code propertyName} has a value that will not be
127 * indexed. This includes {@link Text}, {@link Blob}, and any property
128 * added using {@link #setUnindexedProperty}.
130 public boolean isUnindexedProperty(String propertyName) {
131 Object value = getPropertyMap().get(propertyName);
132 return (value instanceof UnindexedValue) || (value instanceof Text) ||
133 (value instanceof Blob) || (value instanceof EmbeddedEntity);
137 * A convenience method that populates properties from those in the given
138 * container.
140 * <p>This method transfers information about unindexed properties and clones
141 * any mutable values.
143 * @param src The container from which we will populate ourself.
145 public void setPropertiesFrom(PropertyContainer src) {
146 for (Map.Entry<String, Object> entry : src.getPropertyMap().entrySet()) {
147 String name = entry.getKey();
148 Object entryValue = entry.getValue();
150 boolean indexed = entryValue instanceof UnindexedValue;
151 Object valueToAdd = unwrapValue(entryValue);
153 if (valueToAdd instanceof Collection<?>) {
154 Collection<?> srcColl = (Collection<?>) valueToAdd;
155 Collection<Object> destColl = new ArrayList<Object>(srcColl.size());
156 valueToAdd = destColl;
157 for (Object element : srcColl) {
158 destColl.add(cloneIfMutable(element));
160 } else {
161 valueToAdd = cloneIfMutable(valueToAdd);
164 if (indexed) {
165 valueToAdd = new UnindexedValue(valueToAdd);
168 getPropertyMap().put(name, valueToAdd);
172 abstract Map<String, Object> getPropertyMap();
175 * If obj is an {@code UnindexedValue}, returns the value it wraps.
176 * Otherwise, returns {@code obj}.
178 * @param obj may be null
180 static Object unwrapValue(Object obj) {
181 if (obj instanceof UnindexedValue) {
182 return ((UnindexedValue) obj).getValue();
183 } else {
184 return obj;
188 @Override
189 protected Object clone() {
190 throw new UnsupportedOperationException();
194 * Returns a clone of the provided object if it is mutable, otherwise
195 * just return the provided object.
197 private static Object cloneIfMutable(Object obj) {
198 if (obj instanceof Date) {
199 return ((Date) obj).clone();
201 else if (obj instanceof PropertyContainer) {
202 return ((PropertyContainer) obj).clone();
204 return obj;
207 protected void appendPropertiesTo(StringBuilder builder) {
208 for (Map.Entry<String, Object> entry : getPropertyMap().entrySet()) {
209 builder.append('\t')
210 .append(entry.getKey())
211 .append(" = ")
212 .append(entry.getValue())
213 .append('\n');