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    }