    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}