001 /*
002 * file PropertyRequestItem.java
003 *
004 * Licensed Materials - Property of IBM
005 * Restricted Materials of IBM
006 *
007 * (c) Copyright IBM Corporation 2004, 2008. All Rights Reserved.
008 * Note to U.S. Government Users Restricted Rights: Use, duplication or
009 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
010 */
011 package javax.wvcm;
012
013 import java.text.MessageFormat;
014 import java.util.Collections;
015 import java.util.HashMap;
016 import java.util.Iterator;
017 import java.util.Map;
018 import java.util.Map.Entry;
019
020 import javax.wvcm.PropertyNameList.PropertyName;
021
022
023 /**
024 * A specification of the types of objects can be used to create a PropertyRequest.
025 *
026 * @since 1.0
027 */
028 public interface PropertyRequestItem {
029
030 /**
031 * A map of PropertyName to PropertyRequest objects, used to specify one or more
032 * properties whose values are to be read from a resource.
033 * <p>
034 * If the property value is itself a resource (or a data structure containing resources),
035 * then the PropertyRequest value of the entry for the PropertyName of that property
036 * specifies the properties that should be retrieved for that resource.
037 * Such a request can be constructed using the {@link PropertyName#nest} method.
038 * <p>
039 * For example, the following code fragment creates a PropertyRequest that
040 * specifies a request for the {@link Resource#CREATOR_DISPLAY_NAME},
041 * {@link ControllableResource#CHECKED_IN}, and
042 * {@link Resource#LAST_MODIFIED} properties, as well as the
043 * {@link Version#VERSION_NAME} and {@link Resource#CREATION_DATE}
044 * properties of the resource that is the value of the
045 * {@link ControllableResource#CHECKED_IN} property:
046 *
047 * <pre>
048 * PropertyRequest props = new PropertyRequest(
049 * Resource.CREATOR_DISPLAY_NAME,
050 * ControllableResource.CHECKED_IN.nest(
051 * Version.VERSION_NAME,
052 * Resource.CREATION_DATE),
053 * Resource.LAST_MODIFIED);
054 * </pre>
055 */
056 public class PropertyRequest implements PropertyRequestItem, Feedback
057 {
058
059 private Map<PropertyName<?>, PropertyRequest> _itemMap;
060
061 /**
062 * A cached array form of the property request.
063 */
064 private NestedPropertyName<?>[] _itemArray;
065
066 private static final NestedPropertyName<?>[] EMPTY_PNA =
067 new NestedPropertyName[0];
068
069 /**
070 * An empty PropertyRequest.
071 */
072 public static final PropertyRequest EMPTY =
073 new PropertyRequest();
074
075 private static final Map<PropertyName<?>, PropertyRequest> EMPTY_MAP =
076 Collections.emptyMap();
077
078 /**
079 * Constructs a PropertyRequest from an array of PropertyRequestItem objects.
080 * If there are two entries for the same PropertyName, the PropertyRequest values
081 * for those entries are logically merged.
082 *
083 * @param items The array of PropertyRequestItem objects that are to
084 * be combined into a new PropertyRequest.
085 * A <b>null</b> or empty array produces an empty PropertyRequest.
086 */
087 public PropertyRequest(PropertyRequestItem... items)
088 {
089 this(getItemMap(items));
090 }
091
092 /**
093 * Constructs a PropertyRequest from an array of PropertyRequestItem objects.
094 * If there are two entries for the same PropertyName, the PropertyRequest values
095 * for those entries are logically merged.
096 *
097 * @param items The array of PropertyRequestItem objects that are to
098 * be combined into a new PropertyRequest.
099 * A <b>null</b> or empty array produces an empty PropertyRequest.
100 */
101 public PropertyRequest(PropertyName<?>[] items)
102 {
103 this((PropertyRequestItem[]) items);
104 }
105
106 /**
107 * Constructs a PropertyRequest from an array of PropertyRequestItem objects.
108 * If there are two entries for the same PropertyName, the PropertyRequest values
109 * for those entries are logically merged.
110 *
111 * @param items The array of PropertyRequestItem objects that are to
112 * be combined into a new PropertyRequest.
113 * A <b>null</b> or empty array produces an empty PropertyRequest.
114 */
115 public PropertyRequest(NestedPropertyName<?>[] items)
116 {
117 this((PropertyRequestItem[]) items);
118 }
119
120 /**
121 * Constructs a PropertyRequest from a <PropertyName, PropertyRequest> map.
122 */
123 private PropertyRequest(Map<PropertyName<?>, PropertyRequest> itemMap)
124 {
125 _itemMap = itemMap == null || itemMap.isEmpty()? null
126 :Collections.unmodifiableMap(itemMap);
127 }
128
129 private static Map<PropertyName<?>, PropertyRequest>
130 getItemMap(PropertyRequestItem... items)
131 {
132 if (items == null || items.length == 0) {
133 return null;
134 } else {
135 Map<PropertyName<?>, PropertyRequest> itemMap
136 = new HashMap<PropertyName<?>, PropertyRequest>(items.length);
137
138 for (PropertyRequestItem request: items) {
139 if (request == null) {
140 // ignore
141 } else if (request instanceof PropertyName) {
142 put(itemMap, (PropertyName<?>)request, null);
143 } else if (request instanceof NestedPropertyName) {
144 NestedPropertyName<?> npn = (NestedPropertyName<?>)request;
145 put(itemMap, npn.getRoot(), npn.getNested());
146 } else if (request instanceof PropertyNameList) {
147 PropertyNameList list = (PropertyNameList) request;
148
149 for (PropertyName<?> name : list.getPropertyNames())
150 put(itemMap, name, null);
151 } else if (request instanceof PropertyRequest) {
152 Map<PropertyName<?>, PropertyRequest> map =
153 ((PropertyRequest) request).toMap();
154
155 for (Entry<PropertyName<?>, PropertyRequest> entry : map.entrySet())
156 put(itemMap, entry.getKey(), entry.getValue());
157 } else {
158 throw new UnsupportedOperationException(request.getClass().getName()
159 + " cannot be a PropertyRequest item"); //$NON-NLS-1$
160 }
161 }
162
163 return itemMap.isEmpty()? null: itemMap;
164 }
165 }
166
167 private static void put(Map<PropertyName<?>, PropertyRequest> itemMap,
168 PropertyName<?> propertyName,
169 PropertyRequest propertyRequest) {
170 PropertyRequest pr = itemMap.get(propertyName);
171 itemMap.put(propertyName, merge(pr, propertyRequest));
172 }
173
174 private static PropertyRequest merge(PropertyRequest pr1, PropertyRequest pr2) {
175 if (pr1 == null) {
176 return pr2;
177 }
178 if (pr2 == null) {
179 return pr1;
180 }
181 Map<PropertyName<?>, PropertyRequest> itemMap
182 = new HashMap<PropertyName<?>, PropertyRequest>(pr1.size() + pr2.size());
183 Iterator<PropertyName<?>> keys = pr1.toMap().keySet().iterator();
184 while (keys.hasNext()) {
185 PropertyName<?> pName = keys.next();
186 put(itemMap, pName, pr1.toMap().get(pName));
187 }
188 keys = pr2.toMap().keySet().iterator();
189 while (keys.hasNext()) {
190 PropertyName<?> pName = keys.next();
191 put(itemMap, pName, pr2.toMap().get(pName));
192 }
193 PropertyRequest result = new PropertyRequest();
194 result._itemMap = Collections.unmodifiableMap(itemMap);
195 return result;
196 }
197
198 public Map<PropertyName<?>, PropertyRequest> toMap()
199 {
200 return (_itemMap == null) ? EMPTY_MAP : _itemMap;
201 }
202
203 /**
204 * Returns an array containing all of the elements in this PropertyRequest.
205 */
206 public NestedPropertyName<?>[] toArray()
207 {
208 if (_itemMap == null) {
209 return EMPTY_PNA;
210 }
211 if (_itemArray == null) {
212 NestedPropertyName<?>[] itemArray = new NestedPropertyName<?>[_itemMap.size()];
213 int i = 0;
214 for (PropertyName<?> propName: _itemMap.keySet()) {
215 PropertyRequest propRequest = _itemMap.get(propName);
216 itemArray[i++] = propName.nest(propRequest);
217 }
218
219 _itemArray = itemArray;
220 }
221 return _itemArray;
222 }
223
224 /**
225 * @see java.util.Map#isEmpty
226 */
227 public boolean isEmpty()
228 {
229 return toMap().isEmpty();
230 }
231
232 /**
233 * @see java.util.Map#size
234 */
235 public int size()
236 {
237 return toMap().size();
238 }
239
240 /**
241 * @see java.util.Map#get
242 */
243 public PropertyRequest get(PropertyName<?> key)
244 {
245 return toMap().get(key);
246 }
247
248 @Override
249 public boolean equals(Object object)
250 {
251 if (object == null || getClass() != object.getClass()) {
252 return false;
253 }
254 return toMap().equals(((PropertyRequest)object).toMap());
255 }
256
257 @Override
258 public int hashCode()
259 {
260 return toMap().hashCode();
261 }
262
263 @Override
264 public String toString()
265 {
266 StringBuffer builder = new StringBuffer();
267 NestedPropertyName<?>[] items = toArray();
268 builder.append('[');
269 for (int i=0; i<items.length; i++) {
270 if (builder.length() > 1)
271 builder.append(", "); //$NON-NLS-1$
272 builder.append(items[i].toString());
273 }
274 builder.append(']');
275
276 return builder.toString();
277 }
278
279 public PropertyRequest getPropertyRequestForModified() {
280 return null;
281 }
282
283 public PropertyRequest getPropertyRequestForResult() {
284 return this;
285 }
286
287 public boolean isAbortRequested() {
288 return false;
289 }
290
291 public Feedback nest() {
292 return EMPTY;
293 }
294
295 public Feedback nest(PropertyRequest propertyRequest) {
296 return propertyRequest;
297 }
298
299 public Feedback nest(int percentCompleted) {
300 return EMPTY;
301 }
302
303 public Feedback nest(PropertyRequest resultPropertyRequest, int percentCompleted) {
304 return resultPropertyRequest;
305 }
306
307 public String format(String fmt, Object...arguments) {
308 return MessageFormat.format(fmt, arguments);
309 }
310
311 public void notifyActive(String message) {
312 }
313
314 public void notifyWarning(String message) {
315 }
316
317 public void notifyIsModified(Resource resource) {
318 }
319
320 public void notifyPercentComplete(int percentComplete) {
321 }
322
323 }
324
325 /**
326 * A NestedPropertyName consists of the name of a root property whose value is
327 * desired and an optional request for nested properties of the resource (or
328 * resources) referenced by the value of that root property.
329 */
330 public static class NestedPropertyName<T> implements PropertyRequestItem
331 {
332 final private PropertyNameList.PropertyName<T> _root;
333 final private PropertyRequest _nested;
334 private volatile int _hashCode = 0;
335
336 /**
337 * Constructs a NestedPropertyName from its constituent parts.
338 *
339 * @param root The root PropertyName of the request; must not be <b>null</b>.
340 * @param items The PropertyRequest for the value(s) of the root
341 * property; may be <b>null</b> or empty to request no
342 * nested properties.
343 */
344 public NestedPropertyName(PropertyName<T> root,
345 PropertyRequestItem... items) {
346
347 if (root == null) {
348 throw new UnsupportedOperationException
349 ("The root PropertyName of a NestedPropertyName cannot be null."); //$NON-NLS-1$
350 }
351
352 _root = root;
353
354 Map<PropertyName<?>, PropertyRequest> itemMap =
355 PropertyRequest.getItemMap(items);
356
357 _nested =
358 itemMap == null || itemMap.isEmpty()
359 ? null
360 : new PropertyRequest(itemMap);
361 }
362
363 /**
364 * Constructs a NestedPropertyName from its constituent parts.
365 *
366 * @param root The root PropertyName of the request; must not be <b>null</b>.
367 * @param items The PropertyRequest for the value(s) of the root
368 * property; may be <b>null</b> or empty to request no
369 * nested properties.
370 */
371 public NestedPropertyName(PropertyName<T> root,
372 PropertyName<?>[] items)
373 {
374 this(root, (PropertyRequestItem[])items);
375 }
376
377 /**
378 * Constructs a NestedPropertyName from its constituent parts.
379 *
380 * @param root The root PropertyName of the request; must not be <b>null</b>.
381 * @param items The PropertyRequest for the value(s) of the root
382 * property; may be <b>null</b> or empty to request no
383 * nested properties.
384 */
385 public NestedPropertyName(PropertyName<T> root,
386 NestedPropertyName<?>[] items)
387 {
388 this(root, (PropertyRequestItem[])items);
389 }
390
391 /**
392 * @return The root PropertyName of this nested property name object;
393 * will never be <b>null</b>.
394 */
395 public PropertyName<T> getRoot() {
396 return _root;
397 }
398
399 /**
400 * @return The nested PropertyRequest, specifying the properties to be
401 * requested from the resource or resources that are the value of the
402 * root property name of this nested property name.
403 */
404 public PropertyRequest getNested() {
405 return _nested==null ? PropertyRequest.EMPTY: _nested;
406 }
407
408 /**
409 * Indicates whether some other object is "equal to" this one.
410 *
411 * @param o the object to compare with.
412 * @return true if and only if the specified object is a
413 * NestedPropertyName whose root property name and nested
414 * property name list values are equal to those of this object.
415 */
416 @Override
417 public boolean equals(Object o)
418 {
419 if (o == null || this.getClass() != o.getClass()) {
420 return false;
421 }
422 NestedPropertyName<?> npn = (NestedPropertyName<?>) o;
423 return _root.equals(npn.getRoot())
424 && getNested().equals(npn.getNested());
425 }
426
427 /**
428 * Calculate a hash code value for the object.
429 *
430 * @return the hash code for the object.
431 */
432 @Override
433 public int hashCode() {
434 if (_hashCode == 0) {
435 int result = 17;
436 result = 37 * result + _root.hashCode();
437 result = 37 * result + getNested().hashCode();
438 _hashCode = result;
439 }
440 return _hashCode;
441 }
442
443 /**
444 * Returns a string representation of this NestedPropertyName suitable for
445 * diagnostics.
446 *
447 * @return The string representation of this NestedPropertyName formatted as
448 * "property_name [first_nested_property_request, ... ,
449 * last_nested_property_request]"
450 */
451 @Override
452 public String toString() {
453 if (getNested().isEmpty())
454 return _root.toString();
455 else
456 return _root.toString() + getNested().toString();
457 }
458 }
459 }