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