    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.JXPathInvalidAccessException;
    024import org.apache.commons.jxpath.ri.QName;
    025import org.apache.commons.jxpath.ri.model.NodePointer;
    026
    027/**
    028 * Pointer to a null property.
    029 */
    030public class NullPropertyPointer extends PropertyPointer {
    031
    032    private static final long serialVersionUID = 5296593071854982754L;
    033
    034    /**
    035     * The property name.
    036     */
    037    private String propertyName = "*";
    038
    039    /**
    040     * Whether this property is named.
    041     */
    042    private boolean byNameAttribute;
    043
    044    /**
    045     * Constructs a new NullPropertyPointer.
    046     *
    047     * @param parent pointer
    048     */
    049    public NullPropertyPointer(final NodePointer parent) {
    050        super(parent);
    051    }
    052
    053    @Override
    054    public String asPath() {
    055        if (!byNameAttribute) {
    056            return super.asPath();
    057        }
    058        final StringBuilder buffer = new StringBuilder();
    059        buffer.append(getImmediateParentPointer().asPath());
    060        buffer.append("[@name='");
    061        buffer.append(escape(getPropertyName()));
    062        buffer.append("']");
    063        if (index != WHOLE_COLLECTION) {
    064            buffer.append('[').append(index + 1).append(']');
    065        }
    066        return buffer.toString();
    067    }
    068
    069    /**
    070     * Create a "bad factory" JXPathAbstractFactoryException for the specified AbstractFactory.
    071     *
    072     * @param factory AbstractFactory
    073     * @return JXPathAbstractFactoryException
    074     */
    075    private JXPathAbstractFactoryException createBadFactoryException(final AbstractFactory factory) {
    076        return new JXPathAbstractFactoryException("Factory " + factory + " reported success creating object for path: " + asPath()
    077                + " but object was null.  Terminating to avoid stack recursion.");
    078    }
    079
    080    @Override
    081    public NodePointer createChild(final JXPathContext context, final QName qName, final int index) {
    082        return createPath(context).createChild(context, qName, index);
    083    }
    084
    085    @Override
    086    public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) {
    087        return createPath(context).createChild(context, qName, index, value);
    088    }
    089
    090    @Override
    091    public NodePointer createPath(final JXPathContext context) {
    092        NodePointer newParent = parent.createPath(context);
    093        if (isAttribute()) {
    094            return newParent.createAttribute(context, getName());
    095        }
    096        if (parent instanceof NullPointer && parent.equals(newParent)) {
    097            throw createBadFactoryException(context.getFactory());
    098        }
    099        // Consider these two use cases:
    100        // 1. The parent pointer of NullPropertyPointer is
    101        // a PropertyOwnerPointer other than NullPointer. When we call
    102        // createPath on it, it most likely returns itself. We then
    103        // take a PropertyPointer from it and get the PropertyPointer
    104        // to expand the collection for the corresponding property.
    105        //
    106        // 2. The parent pointer of NullPropertyPointer is a NullPointer.
    107        // When we call createPath, it may return a PropertyOwnerPointer
    108        // or it may return anything else, like a DOMNodePointer.
    109        // In the former case we need to do exactly what we did in use
    110        // case 1. In the latter case, we simply request that the
    111        // non-property pointer expand the collection by itself.
    112        if (newParent instanceof PropertyOwnerPointer) {
    113            final PropertyOwnerPointer pop = (PropertyOwnerPointer) newParent;
    114            newParent = pop.getPropertyPointer();
    115        }
    116        return newParent.createChild(context, getName(), getIndex());
    117    }
    118
    119    @Override
    120    public NodePointer createPath(final JXPathContext context, final Object value) {
    121        NodePointer newParent = parent.createPath(context);
    122        if (isAttribute()) {
    123            final NodePointer pointer = newParent.createAttribute(context, getName());
    124            pointer.setValue(value);
    125            return pointer;
    126        }
    127        if (parent instanceof NullPointer && parent.equals(newParent)) {
    128            throw createBadFactoryException(context.getFactory());
    129        }
    130        if (newParent instanceof PropertyOwnerPointer) {
    131            final PropertyOwnerPointer pop = (PropertyOwnerPointer) newParent;
    132            newParent = pop.getPropertyPointer();
    133        }
    134        return newParent.createChild(context, getName(), index, value);
    135    }
    136
    137    @Override
    138    public Object getBaseValue() {
    139        return null;
    140    }
    141
    142    @Override
    143    public Object getImmediateNode() {
    144        return null;
    145    }
    146
    147    @Override
    148    public int getLength() {
    149        return 0;
    150    }
    151
    152    @Override
    153    public QName getName() {
    154        return new QName(propertyName);
    155    }
    156
    157    @Override
    158    public int getPropertyCount() {
    159        return 0;
    160    }
    161
    162    @Override
    163    public String getPropertyName() {
    164        return propertyName;
    165    }
    166
    167    @Override
    168    public String[] getPropertyNames() {
    169        return new String[0];
    170    }
    171
    172    @Override
    173    public NodePointer getValuePointer() {
    174        return new NullPointer(this, new QName(getPropertyName()));
    175    }
    176
    177    @Override
    178    public boolean isActual() {
    179        return false;
    180    }
    181
    182    @Override
    183    protected boolean isActualProperty() {
    184        return false;
    185    }
    186
    187    @Override
    188    public boolean isCollection() {
    189        return getIndex() != WHOLE_COLLECTION;
    190    }
    191
    192    @Override
    193    public boolean isContainer() {
    194        return true;
    195    }
    196
    197    @Override
    198    public boolean isLeaf() {
    199        return true;
    200    }
    201
    202    /**
    203     * Sets the name attribute.
    204     *
    205     * @param attributeValue value to set
    206     */
    207    public void setNameAttributeValue(final String attributeValue) {
    208        this.propertyName = attributeValue;
    209        byNameAttribute = true;
    210    }
    211
    212    @Override
    213    public void setPropertyIndex(final int index) {
    214    }
    215
    216    @Override
    217    public void setPropertyName(final String propertyName) {
    218        this.propertyName = propertyName;
    219    }
    220
    221    @Override
    222    public void setValue(final Object value) {
    223        if (parent == null || parent.isContainer()) {
    224            throw new JXPathInvalidAccessException("Cannot set property " + asPath() + ", the target object is null");
    225        }
    226        if (!(parent instanceof PropertyOwnerPointer) || !((PropertyOwnerPointer) parent).isDynamicPropertyDeclarationSupported()) {
    227            throw new JXPathInvalidAccessException("Cannot set property " + asPath() + ", path does not match a changeable location");
    228        }
    229        // If the parent property owner can create
    230        // a property automatically - let it do so
    231        final PropertyPointer propertyPointer = ((PropertyOwnerPointer) parent).getPropertyPointer();
    232        propertyPointer.setPropertyName(propertyName);
    233        propertyPointer.setValue(value);
    234    }
    235}