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
;
14 import java
.util
.regex
.Pattern
;
17 * A mutable property container.
20 public abstract class PropertyContainer
implements Serializable
, Cloneable
{
21 static final Pattern RESERVED_NAME
= Pattern
.compile("^__.*__$");
23 PropertyContainer() {}
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
));
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
);
51 * Returns true if a property has been set. This function can
52 * be used to test if a property has been specifically set
55 * @return true iff the property named {@code propertyName} exists.
57 public boolean hasProperty(String propertyName
) {
58 return getPropertyMap().containsKey(propertyName
);
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
);
72 * Sets the property named, {@code propertyName}, to {@code value}.
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}.
78 * All {@code Collections} are prone
79 * to losing their sort order and their original types as they are
80 * stored in the datastore. For example, a {@code TreeSet} may be
81 * returned as a {@code List} from {@link #getProperty}, with an
82 * arbitrary re-ordering of elements.
84 * Overrides any existing value for this property, whether indexed or
87 * Note that {@link Blob}, {@link Text} and {@link EmbeddedEntity} property
88 * values are never indexed by the built-in single property indexes. To store
89 * other types without being indexed, use {@link #setUnindexedProperty}.
91 * @param value may be one of the supported datatypes or a heterogeneous
92 * {@code Collection} of one of the supported datatypes.
94 * @throws IllegalArgumentException If the value is not of a type that
95 * the data store supports.
97 * @see #setUnindexedProperty
99 public void setProperty(String propertyName
, Object value
) {
100 DataTypeUtils
.checkSupportedValue(propertyName
, value
);
101 getPropertyMap().put(propertyName
, value
);
105 * Like {@code #setProperty}, but doesn't index the property in the built-in
106 * single property indexes.
108 * @param value may be one of the supported datatypes, or a heterogeneous
109 * {@code Collection} of one of the supported datatypes.
111 * Overrides any existing value for this property, whether indexed or
114 * @throws IllegalArgumentException If the value is not of a type that
115 * the data store supports.
119 public void setUnindexedProperty(String propertyName
, Object value
) {
120 DataTypeUtils
.checkSupportedValue(propertyName
, value
);
121 getPropertyMap().put(propertyName
, new UnindexedValue(value
));
125 * Returns true if {@code propertyName} has a value that will not be
126 * indexed. This includes {@link Text}, {@link Blob}, and any property
127 * added using {@link #setUnindexedProperty}.
129 public boolean isUnindexedProperty(String propertyName
) {
130 Object value
= getPropertyMap().get(propertyName
);
131 return (value
instanceof UnindexedValue
) || (value
instanceof Text
) ||
132 (value
instanceof Blob
) || (value
instanceof EmbeddedEntity
);
136 * A convenience method that populates properties from those in the given
139 * <p>This method transfers information about unindexed properties and clones
140 * any mutable values.
142 * @param src The container from which we will populate ourself.
144 public void setPropertiesFrom(PropertyContainer src
) {
145 for (Map
.Entry
<String
, Object
> entry
: src
.getPropertyMap().entrySet()) {
146 String name
= entry
.getKey();
147 Object entryValue
= entry
.getValue();
149 boolean indexed
= entryValue
instanceof UnindexedValue
;
150 Object valueToAdd
= unwrapValue(entryValue
);
152 if (valueToAdd
instanceof Collection
<?
>) {
153 Collection
<?
> srcColl
= (Collection
<?
>) valueToAdd
;
154 Collection
<Object
> destColl
= new ArrayList
<Object
>(srcColl
.size());
155 valueToAdd
= destColl
;
156 for (Object element
: srcColl
) {
157 destColl
.add(cloneIfMutable(element
));
160 valueToAdd
= cloneIfMutable(valueToAdd
);
164 valueToAdd
= new UnindexedValue(valueToAdd
);
167 getPropertyMap().put(name
, valueToAdd
);
171 abstract Map
<String
, Object
> getPropertyMap();
174 * If obj is an {@code UnindexedValue}, returns the value it wraps.
175 * Otherwise, returns {@code obj}.
177 * @param obj may be null
179 static Object
unwrapValue(Object obj
) {
180 if (obj
instanceof UnindexedValue
) {
181 return ((UnindexedValue
) obj
).getValue();
188 protected Object
clone() {
189 throw new UnsupportedOperationException();
193 * Returns a clone of the provided object if it is mutable, otherwise
194 * just return the provided object.
196 private static Object
cloneIfMutable(Object obj
) {
197 if (obj
instanceof Date
) {
198 return ((Date
) obj
).clone();
200 else if (obj
instanceof PropertyContainer
) {
201 return ((PropertyContainer
) obj
).clone();
206 protected void appendPropertiesTo(StringBuilder builder
) {
207 for (Map
.Entry
<String
, Object
> entry
: getPropertyMap().entrySet()) {
209 .append(entry
.getKey())
211 .append(entry
.getValue())