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