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
018package org.apache.commons.jxpath.ri.model.beans;
019
020import org.apache.commons.jxpath.AbstractFactory;
021import org.apache.commons.jxpath.JXPathAbstractFactoryException;
022import org.apache.commons.jxpath.JXPathContext;
023import org.apache.commons.jxpath.JXPathIntrospector;
024import org.apache.commons.jxpath.ri.QName;
025import org.apache.commons.jxpath.ri.model.NodePointer;
026import org.apache.commons.jxpath.util.ValueUtils;
027
028/**
029 * A pointer allocated by a PropertyOwnerPointer to represent the value of a property of the parent object.
030 */
031public abstract class PropertyPointer extends NodePointer {
032
033    private static final long serialVersionUID = 1L;
034    /**
035     * Marks a property as unspecified.
036     */
037    public static final int UNSPECIFIED_PROPERTY = Integer.MIN_VALUE;
038    private static final Object UNINITIALIZED = new Object();
039
040    /** Property index */
041    protected int propertyIndex = UNSPECIFIED_PROPERTY;
042
043    /** Owning object */
044    protected Object bean;
045
046    /**
047     * Supports {@link #getImmediateNode()}.
048     */
049    private Object value = UNINITIALIZED;
050
051    /**
052     * Takes a JavaBean, a descriptor of a property of that object and an offset within that property (starting with 0).
053     *
054     * @param parent parent pointer
055     */
056    public PropertyPointer(final NodePointer parent) {
057        super(parent);
058    }
059
060    @Override
061    public int compareChildNodePointers(final NodePointer pointer1, final NodePointer pointer2) {
062        return getValuePointer().compareChildNodePointers(pointer1, pointer2);
063    }
064
065    @Override
066    public NodePointer createChild(final JXPathContext context, final QName qName, final int index) {
067        final PropertyPointer prop = (PropertyPointer) clone();
068        if (qName != null) {
069            prop.setPropertyName(qName.toString());
070        }
071        prop.setIndex(index);
072        return prop.createPath(context);
073    }
074
075    @Override
076    public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) {
077        final PropertyPointer prop = (PropertyPointer) clone();
078        if (qName != null) {
079            prop.setPropertyName(qName.toString());
080        }
081        prop.setIndex(index);
082        return prop.createPath(context, value);
083    }
084
085    @Override
086    public NodePointer createPath(final JXPathContext context) {
087        if (getImmediateNode() == null) {
088            final AbstractFactory factory = getAbstractFactory(context);
089            final int inx = index == WHOLE_COLLECTION ? 0 : index;
090            final boolean success = factory.createObject(context, this, getBean(), getPropertyName(), inx);
091            if (!success) {
092                throw new JXPathAbstractFactoryException("Factory " + factory + " could not create an object for path: " + asPath());
093            }
094        }
095        return this;
096    }
097
098    @Override
099    public NodePointer createPath(final JXPathContext context, final Object value) {
100        // If necessary, expand collection
101        if (index != WHOLE_COLLECTION && index >= getLength()) {
102            createPath(context);
103        }
104        setValue(value);
105        return this;
106    }
107
108    @Override
109    public boolean equals(final Object object) {
110        if (object == this) {
111            return true;
112        }
113        if (!(object instanceof PropertyPointer)) {
114            return false;
115        }
116        final PropertyPointer other = (PropertyPointer) object;
117        if (parent != other.parent && (parent == null || !parent.equals(other.parent))) {
118            return false;
119        }
120        if (getPropertyIndex() != other.getPropertyIndex() || !getPropertyName().equals(other.getPropertyName())) {
121            return false;
122        }
123        final int iThis = index == WHOLE_COLLECTION ? 0 : index;
124        final int iOther = other.index == WHOLE_COLLECTION ? 0 : other.index;
125        return iThis == iOther;
126    }
127
128    /**
129     * Gets the parent bean.
130     *
131     * @return Object
132     */
133    public Object getBean() {
134        if (bean == null) {
135            bean = getImmediateParentPointer().getNode();
136        }
137        return bean;
138    }
139
140    @Override
141    public Object getImmediateNode() {
142        if (value == UNINITIALIZED) {
143            value = index == WHOLE_COLLECTION ? ValueUtils.getValue(getBaseValue()) : ValueUtils.getValue(getBaseValue(), index);
144        }
145        return value;
146    }
147
148    /**
149     * Returns a NodePointer that can be used to access the currently selected property value.
150     *
151     * @return NodePointer
152     */
153    @Override
154    public NodePointer getImmediateValuePointer() {
155        return newChildNodePointer((NodePointer) clone(), getName(), getImmediateNode());
156    }
157
158    /**
159     * If the property contains a collection, then the length of that collection, otherwise - 1.
160     *
161     * @return int length
162     */
163    @Override
164    public int getLength() {
165        final Object baseValue = getBaseValue();
166        return baseValue == null ? 1 : ValueUtils.getLength(baseValue);
167    }
168
169    @Override
170    public QName getName() {
171        return new QName(null, getPropertyName());
172    }
173
174    /**
175     * Count the number of properties represented.
176     *
177     * @return int
178     */
179    public abstract int getPropertyCount();
180
181    /**
182     * Gets the property index.
183     *
184     * @return int index
185     */
186    public int getPropertyIndex() {
187        return propertyIndex;
188    }
189
190    /**
191     * Gets the property name.
192     *
193     * @return String property name.
194     */
195    public abstract String getPropertyName();
196
197    /**
198     * Gets the names of the included properties.
199     *
200     * @return String[]
201     */
202    public abstract String[] getPropertyNames();
203
204    @Override
205    public int hashCode() {
206        return getImmediateParentPointer().hashCode() + propertyIndex + index;
207    }
208
209    @Override
210    public boolean isActual() {
211        if (!isActualProperty()) {
212            return false;
213        }
214        return super.isActual();
215    }
216
217    /**
218     * Tests whether this pointer references an actual property.
219     *
220     * @return true if actual
221     */
222    protected abstract boolean isActualProperty();
223
224    @Override
225    public boolean isCollection() {
226        final Object value = getBaseValue();
227        return value != null && ValueUtils.isCollection(value);
228    }
229
230    @Override
231    public boolean isLeaf() {
232        final Object value = getNode();
233        return value == null || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic();
234    }
235
236    /**
237     * Sets the property index.
238     *
239     * @param index property index
240     */
241    public void setPropertyIndex(final int index) {
242        if (propertyIndex != index) {
243            propertyIndex = index;
244            setIndex(WHOLE_COLLECTION);
245        }
246    }
247
248    /**
249     * Sets the property name.
250     *
251     * @param propertyName property name to set.
252     */
253    public abstract void setPropertyName(String propertyName);
254}