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}