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 */
017package org.apache.commons.jxpath.ri.model.dynamic;
018
019import java.util.Arrays;
020import java.util.Map;
021
022import org.apache.commons.jxpath.AbstractFactory;
023import org.apache.commons.jxpath.DynamicPropertyHandler;
024import org.apache.commons.jxpath.JXPathAbstractFactoryException;
025import org.apache.commons.jxpath.JXPathContext;
026import org.apache.commons.jxpath.JXPathInvalidAccessException;
027import org.apache.commons.jxpath.ri.model.NodePointer;
028import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
029import 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 19:46:46 +0200 (Fr, 02 Mai 2008) $
036 */
037public 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}