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}