    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;
    019
    020import java.io.Serializable;
    021import java.util.HashMap;
    022
    023import org.apache.commons.jxpath.Pointer;
    024import org.apache.commons.jxpath.ri.model.NodeIterator;
    025import org.apache.commons.jxpath.ri.model.NodePointer;
    026
    027/**
    028 * Namespace resolver for {@link JXPathContextReferenceImpl}.
    029 */
    030public class NamespaceResolver implements Cloneable, Serializable {
    031
    032    private static final long serialVersionUID = 1085590057838651311L;
    033
    034    /**
    035     * Find the namespace prefix for the specified namespace URI and NodePointer.
    036     *
    037     * @param pointer      location
    038     * @param namespaceURI to check
    039     * @return prefix if found
    040     * @since JXPath 1.3
    041     */
    042    protected static String getPrefix(final NodePointer pointer, final String namespaceURI) {
    043        NodePointer currentPointer = pointer;
    044        while (currentPointer != null) {
    045            final NodeIterator ni = currentPointer.namespaceIterator();
    046            for (int position = 1; ni != null && ni.setPosition(position); position++) {
    047                final NodePointer nsPointer = ni.getNodePointer();
    048                final String uri = nsPointer.getNamespaceURI();
    049                if (uri.equals(namespaceURI)) {
    050                    final String prefix = nsPointer.getName().getName();
    051                    if (!"".equals(prefix)) {
    052                        return prefix;
    053                    }
    054                }
    055            }
    056            currentPointer = currentPointer.getParent();
    057        }
    058        return null;
    059    }
    060
    061    /** Parent NamespaceResolver. */
    062    protected final NamespaceResolver parent;
    063
    064    /** Namespace map. */
    065    protected HashMap<String, String> namespaceMap = new HashMap<>();
    066
    067    /** Reverse lookup map */
    068    protected HashMap<String, String> reverseMap = new HashMap<>();
    069
    070    /** Node pointer. */
    071    protected NodePointer pointer;
    072
    073    /**
    074     * Whether this instance is sealed.
    075     */
    076    private boolean sealed;
    077
    078    /**
    079     * Constructs a new NamespaceResolver.
    080     */
    081    public NamespaceResolver() {
    082        this(null);
    083    }
    084
    085    /**
    086     * Constructs a new NamespaceResolver.
    087     *
    088     * @param parent NamespaceResolver
    089     */
    090    public NamespaceResolver(final NamespaceResolver parent) {
    091        this.parent = parent;
    092    }
    093
    094    @Override
    095    public Object clone() {
    096        try {
    097            final NamespaceResolver result = (NamespaceResolver) super.clone();
    098            result.sealed = false;
    099            return result;
    100        } catch (final CloneNotSupportedException e) {
    101            // Of course, it's supported.
    102            e.printStackTrace();
    103            return null;
    104        }
    105    }
    106
    107    /**
    108     * Given a prefix, returns an externally registered namespace URI.
    109     *
    110     * @param prefix The namespace prefix to look up
    111     * @return namespace URI or null if the prefix is undefined.
    112     * @since JXPath 1.3
    113     */
    114    protected synchronized String getExternallyRegisteredNamespaceURI(final String prefix) {
    115        final String uri = namespaceMap.get(prefix);
    116        return uri == null && parent != null ? parent.getExternallyRegisteredNamespaceURI(prefix) : uri;
    117    }
    118
    119    /**
    120     * Gets the nearest prefix found that matches an externally-registered namespace.
    121     *
    122     * @param namespaceURI the ns URI to check.
    123     * @return String prefix if found.
    124     * @since JXPath 1.3
    125     */
    126    protected synchronized String getExternallyRegisteredPrefix(final String namespaceURI) {
    127        final String prefix = reverseMap.get(namespaceURI);
    128        return prefix == null && parent != null ? parent.getExternallyRegisteredPrefix(namespaceURI) : prefix;
    129    }
    130
    131    /**
    132     * Gets the namespace context pointer.
    133     *
    134     * @return Pointer
    135     */
    136    public Pointer getNamespaceContextPointer() {
    137        if (pointer == null && parent != null) {
    138            return parent.getNamespaceContextPointer();
    139        }
    140        return pointer;
    141    }
    142
    143    /**
    144     * Given a prefix, returns a registered namespace URI. If the requested prefix was not defined explicitly using the registerNamespace method, JXPathContext
    145     * will then check the context node to see if the prefix is defined there. See {@link #setNamespaceContextPointer(NodePointer) setNamespaceContextPointer}.
    146     *
    147     * @param prefix The namespace prefix to look up
    148     * @return namespace URI or null if the prefix is undefined.
    149     */
    150    public synchronized String getNamespaceURI(final String prefix) {
    151        final String uri = getExternallyRegisteredNamespaceURI(prefix);
    152        return uri == null && pointer != null ? pointer.getNamespaceURI(prefix) : uri;
    153    }
    154
    155    /**
    156     * Gets the prefix associated with the specifed namespace URI.
    157     *
    158     * @param namespaceURI the ns URI to check.
    159     * @return String prefix
    160     */
    161    public synchronized String getPrefix(final String namespaceURI) {
    162        final String prefix = getExternallyRegisteredPrefix(namespaceURI);
    163        return prefix == null && pointer != null ? getPrefix(pointer, namespaceURI) : prefix;
    164    }
    165
    166    /**
    167     * Tests whether this NamespaceResolver has been sealed.
    168     *
    169     * @return boolean
    170     */
    171    public boolean isSealed() {
    172        return sealed;
    173    }
    174
    175    /**
    176     * Registers a namespace prefix.
    177     *
    178     * @param prefix       A namespace prefix
    179     * @param namespaceURI A URI for that prefix
    180     */
    181    public synchronized void registerNamespace(final String prefix, final String namespaceURI) {
    182        if (isSealed()) {
    183            throw new IllegalStateException("Cannot register namespaces on a sealed NamespaceResolver");
    184        }
    185        namespaceMap.put(prefix, namespaceURI);
    186        reverseMap.put(namespaceURI, prefix);
    187    }
    188
    189    /**
    190     * Seal this {@link NamespaceResolver}.
    191     */
    192    public void seal() {
    193        sealed = true;
    194        if (parent != null) {
    195            parent.seal();
    196        }
    197    }
    198
    199    /**
    200     * Register a namespace for the expression context.
    201     *
    202     * @param pointer the Pointer to set.
    203     */
    204    public void setNamespaceContextPointer(final NodePointer pointer) {
    205        this.pointer = pointer;
    206    }
    207}