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 */
011package javax.wvcm;
012
013import java.text.MessageFormat;
014import java.util.Collections;
015import java.util.HashMap;
016import java.util.Iterator;
017import java.util.Map;
018import java.util.Map.Entry;
019
020import 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 */
028public 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}