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