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    package org.apache.commons.jxpath.ri.model.beans;
018    
019    import java.util.Locale;
020    
021    import org.apache.commons.jxpath.JXPathInvalidAccessException;
022    import org.apache.commons.jxpath.ri.Compiler;
023    import org.apache.commons.jxpath.ri.QName;
024    import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
025    import org.apache.commons.jxpath.ri.compiler.NodeTest;
026    import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
027    import org.apache.commons.jxpath.ri.model.NodeIterator;
028    import org.apache.commons.jxpath.ri.model.NodePointer;
029    import org.apache.commons.jxpath.util.ValueUtils;
030    
031    /**
032     * A pointer describing a node that has properties, each of which could be
033     * a collection.
034     *
035     * @author Dmitri Plotnikov
036     * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
037     */
038    public abstract class PropertyOwnerPointer extends NodePointer {
039        private static final Object UNINITIALIZED = new Object();
040    
041        private Object value = UNINITIALIZED;
042    
043        public NodeIterator childIterator(NodeTest test, boolean reverse,
044                NodePointer startWith) {
045            if (test == null) {
046                return createNodeIterator(null, reverse, startWith);
047            }
048            if (test instanceof NodeNameTest) {
049                NodeNameTest nodeNameTest = (NodeNameTest) test;
050                QName testName = nodeNameTest.getNodeName();
051                if (isValidProperty(testName)) {
052                    return createNodeIterator(nodeNameTest.isWildcard() ? null
053                            : testName.toString(), reverse, startWith);
054                }
055                return null;
056            }
057            return test instanceof NodeTypeTest && ((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_NODE
058                    ? createNodeIterator(null, reverse, startWith) : null;
059        }
060    
061        /**
062         * Create a NodeIterator.
063         * @param property property name
064         * @param reverse whether to iterate in reverse
065         * @param startWith first pointer to return
066         * @return NodeIterator
067         */
068        public NodeIterator createNodeIterator(String property, boolean reverse,
069                NodePointer startWith) {
070            return new PropertyIterator(this, property, reverse, startWith);
071        }
072    
073        public NodeIterator attributeIterator(QName name) {
074            return new BeanAttributeIterator(this, name);
075        }
076    
077        /**
078         * Create a new PropertyOwnerPointer.
079         * @param parent parent pointer
080         * @param locale Locale
081         */
082        protected PropertyOwnerPointer(NodePointer parent, Locale locale) {
083            super(parent, locale);
084        }
085    
086        /**
087         * Create a new PropertyOwnerPointer.
088         * @param parent pointer
089         */
090        protected PropertyOwnerPointer(NodePointer parent) {
091            super(parent);
092        }
093    
094        public void setIndex(int index) {
095            if (this.index != index) {
096                super.setIndex(index);
097                value = UNINITIALIZED;
098            }
099        }
100    
101        public Object getImmediateNode() {
102            if (value == UNINITIALIZED) {
103                value = index == WHOLE_COLLECTION ? ValueUtils.getValue(getBaseValue())
104                        : ValueUtils.getValue(getBaseValue(), index);
105            }
106            return value;
107        }
108    
109        public abstract QName getName();
110    
111        /**
112         * Learn whether <code>name</code> is a valid child name for this PropertyOwnerPointer.
113         * @param name the QName to test
114         * @return <code>true</code> if <code>QName</code> is a valid property name.
115         * @since JXPath 1.3
116         */
117        public boolean isValidProperty(QName name) {
118            return isDefaultNamespace(name.getPrefix());
119        }
120    
121        /**
122         * Throws an exception if you try to change the root element, otherwise
123         * forwards the call to the parent pointer.
124         * @param value to set
125         */
126        public void setValue(Object value) {
127            this.value = value;
128            if (parent != null) {
129                if (parent.isContainer()) {
130                    parent.setValue(value);
131                }
132                else {
133                    if (index == WHOLE_COLLECTION) {
134                        throw new UnsupportedOperationException(
135                            "Cannot setValue of an object that is not "
136                                + "some other object's property");
137                    }
138                    throw new JXPathInvalidAccessException(
139                        "The specified collection element does not exist: " + this);
140                }
141            }
142            else {
143                throw new UnsupportedOperationException(
144                    "Cannot replace the root object");
145            }
146        }
147    
148        /**
149         * If this is a root node pointer, throws an exception; otherwise
150         * forwards the call to the parent node.
151         */
152        public void remove() {
153            this.value = null;
154            if (parent != null) {
155                parent.remove();
156            }
157            else {
158                throw new UnsupportedOperationException(
159                    "Cannot remove an object that is not "
160                        + "some other object's property or a collection element");
161            }
162        }
163    
164        /**
165         * Get a PropertyPointer for this PropertyOwnerPointer.
166         * @return PropertyPointer
167         */
168        public abstract PropertyPointer getPropertyPointer();
169    
170        /**
171         * Learn whether dynamic property declaration is supported.
172         * @return true if the property owner can set a property "does not exist".
173         *         A good example is a Map. You can always assign a value to any
174         *         key even if it has never been "declared".
175         */
176        public boolean isDynamicPropertyDeclarationSupported() {
177            return false;
178        }
179    
180        public int compareChildNodePointers(NodePointer pointer1,
181                NodePointer pointer2) {
182            int r = pointer1.getName().toString().compareTo(pointer2.getName().toString());
183            return r == 0 ? pointer1.getIndex() - pointer2.getIndex() : r;
184        }
185    }