    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}