001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.jxpath.ri.model.dynamic;
018    
019    import java.util.Arrays;
020    import java.util.Map;
021    
022    import org.apache.commons.jxpath.AbstractFactory;
023    import org.apache.commons.jxpath.DynamicPropertyHandler;
024    import org.apache.commons.jxpath.JXPathAbstractFactoryException;
025    import org.apache.commons.jxpath.JXPathContext;
026    import org.apache.commons.jxpath.JXPathInvalidAccessException;
027    import org.apache.commons.jxpath.ri.model.NodePointer;
028    import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
029    import org.apache.commons.jxpath.util.ValueUtils;
030    
031    /**
032     * Pointer pointing to a property of an object with dynamic properties.
033     *
034     * @author Dmitri Plotnikov
035     * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
036     */
037    public class DynamicPropertyPointer extends PropertyPointer {
038    
039        private static final long serialVersionUID = -5720585681149150822L;
040    
041        private DynamicPropertyHandler handler;
042        private String name;
043        private String[] names;
044        private String requiredPropertyName;
045    
046        /**
047         * Create a new DynamicPropertyPointer.
048         * @param parent pointer
049         * @param handler DynamicPropertyHandler
050         */
051        public DynamicPropertyPointer(NodePointer parent,
052                DynamicPropertyHandler handler) {
053            super(parent);
054            this.handler = handler;
055        }
056    
057        /**
058         * This type of node is auxiliary.
059         * @return true
060         */
061        public boolean isContainer() {
062            return true;
063        }
064    
065        /**
066         * Number of the DP object's properties.
067         * @return int
068         */
069        public int getPropertyCount() {
070            return getPropertyNames().length;
071        }
072    
073        /**
074         * Names of all properties, sorted alphabetically.
075         * @return String[]
076         */
077        public String[] getPropertyNames() {
078            if (names == null) {
079                String[] allNames = handler.getPropertyNames(getBean());
080                names = new String[allNames.length];
081                for (int i = 0; i < names.length; i++) {
082                    names[i] = allNames[i];
083                }
084                Arrays.sort(names);
085                if (requiredPropertyName != null) {
086                    int inx = Arrays.binarySearch(names, requiredPropertyName);
087                    if (inx < 0) {
088                        allNames = names;
089                        names = new String[allNames.length + 1];
090                        names[0] = requiredPropertyName;
091                        System.arraycopy(allNames, 0, names, 1, allNames.length);
092                        Arrays.sort(names);
093                    }
094                }
095            }
096            return names;
097        }
098    
099        /**
100         * Returns the name of the currently selected property or "*"
101         * if none has been selected.
102         * @return String
103         */
104        public String getPropertyName() {
105            if (name == null) {
106                String[] names = getPropertyNames();
107                name = propertyIndex >= 0 && propertyIndex < names.length ? names[propertyIndex] : "*";
108            }
109            return name;
110        }
111    
112        /**
113         * Select a property by name.  If the supplied name is
114         * not one of the object's existing properties, it implicitly
115         * adds this name to the object's property name list. It does not
116         * set the property value though. In order to set the property
117         * value, call setValue().
118         * @param propertyName to set
119         */
120        public void setPropertyName(String propertyName) {
121            setPropertyIndex(UNSPECIFIED_PROPERTY);
122            this.name = propertyName;
123            requiredPropertyName = propertyName;
124            if (names != null && Arrays.binarySearch(names, propertyName) < 0) {
125                names = null;
126            }
127        }
128    
129        /**
130         * Index of the currently selected property in the list of all
131         * properties sorted alphabetically.
132         * @return int
133         */
134        public int getPropertyIndex() {
135            if (propertyIndex == UNSPECIFIED_PROPERTY) {
136                String[] names = getPropertyNames();
137                for (int i = 0; i < names.length; i++) {
138                    if (names[i].equals(name)) {
139                        setPropertyIndex(i);
140                        break;
141                    }
142                }
143            }
144            return super.getPropertyIndex();
145        }
146    
147        /**
148         * Index a property by its index in the list of all
149         * properties sorted alphabetically.
150         * @param index to set
151         */
152        public void setPropertyIndex(int index) {
153            if (propertyIndex != index) {
154                super.setPropertyIndex(index);
155                name = null;
156            }
157        }
158    
159        /**
160         * Returns the value of the property, not an element of the collection
161         * represented by the property, if any.
162         * @return Object
163         */
164        public Object getBaseValue() {
165            return handler.getProperty(getBean(), getPropertyName());
166        }
167    
168        /**
169         * If index == WHOLE_COLLECTION, the value of the property, otherwise
170         * the value of the index'th element of the collection represented by the
171         * property. If the property is not a collection, index should be zero
172         * and the value will be the property itself.
173         * @return Object
174         */
175        public Object getImmediateNode() {
176            Object value;
177            if (index == WHOLE_COLLECTION) {
178                value = ValueUtils.getValue(handler.getProperty(
179                        getBean(),
180                        getPropertyName()));
181            }
182            else {
183                value = ValueUtils.getValue(handler.getProperty(
184                        getBean(),
185                        getPropertyName()), index);
186            }
187            return value;
188        }
189    
190        /**
191         * A dynamic property is always considered actual - all keys are apparently
192         * existing with possibly the value of null.
193         * @return boolean
194         */
195        protected boolean isActualProperty() {
196            return true;
197        }
198    
199        /**
200         * If index == WHOLE_COLLECTION, change the value of the property, otherwise
201         * change the value of the index'th element of the collection
202         * represented by the property.
203         * @param value to set
204         */
205        public void setValue(Object value) {
206            if (index == WHOLE_COLLECTION) {
207                handler.setProperty(getBean(), getPropertyName(), value);
208            }
209            else {
210                ValueUtils.setValue(
211                    handler.getProperty(getBean(), getPropertyName()),
212                    index,
213                    value);
214            }
215        }
216    
217        public NodePointer createPath(JXPathContext context) {
218            // Ignore the name passed to us, use our own data
219            Object collection = getBaseValue();
220            if (collection == null) {
221                AbstractFactory factory = getAbstractFactory(context);
222                boolean success =
223                    factory.createObject(
224                        context,
225                        this,
226                        getBean(),
227                        getPropertyName(),
228                        0);
229                if (!success) {
230                    throw new JXPathAbstractFactoryException(
231                        "Factory could not create an object for path: " + asPath());
232                }
233                collection = getBaseValue();
234            }
235    
236            if (index != WHOLE_COLLECTION) {
237                if (index < 0) {
238                    throw new JXPathInvalidAccessException("Index is less than 1: "
239                            + asPath());
240                }
241    
242                if (index >= getLength()) {
243                    collection = ValueUtils.expandCollection(collection, index + 1);
244                    handler.setProperty(getBean(), getPropertyName(), collection);
245                }
246            }
247    
248            return this;
249        }
250    
251        public NodePointer createPath(JXPathContext context, Object value) {
252            if (index == WHOLE_COLLECTION) {
253                handler.setProperty(getBean(), getPropertyName(), value);
254            }
255            else {
256                createPath(context);
257                ValueUtils.setValue(getBaseValue(), index, value);
258            }
259            return this;
260        }
261    
262        public void remove() {
263            if (index == WHOLE_COLLECTION) {
264                removeKey();
265            }
266            else if (isCollection()) {
267                Object collection = ValueUtils.remove(getBaseValue(), index);
268                handler.setProperty(getBean(), getPropertyName(), collection);
269            }
270            else if (index == 0) {
271                removeKey();
272            }
273        }
274    
275        /**
276         * Remove the current property.
277         */
278        private void removeKey() {
279            Object bean = getBean();
280            if (bean instanceof Map) {
281                ((Map) bean).remove(getPropertyName());
282            }
283            else {
284                handler.setProperty(bean, getPropertyName(), null);
285            }
286        }
287    
288        public String asPath() {
289            StringBuffer buffer = new StringBuffer();
290            buffer.append(getImmediateParentPointer().asPath());
291            if (buffer.length() == 0) {
292                buffer.append("/.");
293            }
294            else if (buffer.charAt(buffer.length() - 1) == '/') {
295                buffer.append('.');
296            }
297            buffer.append("[@name='");
298            buffer.append(escape(getPropertyName()));
299            buffer.append("']");
300            if (index != WHOLE_COLLECTION && isCollection()) {
301                buffer.append('[').append(index + 1).append(']');
302            }
303            return buffer.toString();
304        }
305    
306    }