1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: NamespaceMappings.java 469648 2006-10-31 20:52:27Z minchau $
20 */
21package org.apache.xml.serializer;
22
23import java.util.Enumeration;
24import java.util.Hashtable;
25
26import org.xml.sax.ContentHandler;
27import org.xml.sax.SAXException;
28
29/**
30 * This class keeps track of the currently defined namespaces. Conceptually the
31 * prefix/uri/depth triplets are pushed on a stack pushed on a stack. The depth
32 * indicates the nesting depth of the element for which the mapping was made.
33 *
34 * <p>For example:
35 * <pre>
36 * <chapter xmlns:p1="def">
37 *   <paragraph xmlns:p2="ghi">
38 *      <sentance xmlns:p3="jkl">
39 *      </sentance>
40 *    </paragraph>
41 *    <paragraph xlmns:p4="mno">
42 *    </paragraph>
43 * </chapter>
44 * </pre>
45 *
46 * When the <chapter> element is encounted the prefix "p1" associated with uri
47 * "def" is pushed on the stack with depth 1.
48 * When the first <paragraph> is encountered "p2" and "ghi" are pushed with
49 * depth 2.
50 * When the <sentance> is encountered "p3" and "jkl" are pushed with depth 3.
51 * When </sentance> occurs the popNamespaces(3) will pop "p3"/"jkl" off the
52 * stack.  Of course popNamespaces(2) would pop anything with depth 2 or
53 * greater.
54 *
55 * So prefix/uri pairs are pushed and poped off the stack as elements are
56 * processed.  At any given moment of processing the currently visible prefixes
57 * are on the stack and a prefix can be found given a uri, or a uri can be found
58 * given a prefix.
59 *
60 * This class is intended for internal use only.  However, it is made public because
61 * other packages require it.
62 * @xsl.usage internal
63 */
64public class NamespaceMappings
65{
66    /**
67     * This member is continually incremented when new prefixes need to be
68     * generated. ("ns0"  "ns1" ...)
69     */
70    private int count = 0;
71
72    /**
73     * Each entry (prefix) in this hashtable points to a Stack of URIs
74     * This table maps a prefix (String) to a Stack of NamespaceNodes.
75     * All Namespace nodes in that retrieved stack have the same prefix,
76     * though possibly different URI's or depths. Such a stack must have
77     * mappings at deeper depths push later on such a stack.  Mappings pushed
78     * earlier on the stack will have smaller values for MappingRecord.m_declarationDepth.
79     */
80    private Hashtable m_namespaces = new Hashtable();
81
82    /**
83     * This stack is used as a convenience.
84     * It contains the pushed NamespaceNodes (shallowest
85     * to deepest) and is used to delete NamespaceNodes
86     * when leaving the current element depth
87     * to returning to the parent. The mappings of the deepest
88     * depth can be popped of the top and the same node
89     * can be removed from the appropriate prefix stack.
90     *
91     * All prefixes pushed at the current depth can be
92     * removed at the same time by using this stack to
93     * ensure prefix/uri map scopes are closed correctly.
94     */
95    private Stack m_nodeStack = new Stack();
96
97    private static final String EMPTYSTRING = "";
98    private static final String XML_PREFIX = "xml"; // was "xmlns"
99
100    /**
101     * Default constructor
102     * @see java.lang.Object#Object()
103     */
104    public NamespaceMappings()
105    {
106        initNamespaces();
107    }
108
109    /**
110     * This method initializes the namespace object with appropriate stacks
111     * and predefines a few prefix/uri pairs which always exist.
112     */
113    private void initNamespaces()
114    {
115        // The initial prefix mappings will never be deleted because they are at element depth -1
116        // (a kludge)
117
118        // Define the default namespace (initially maps to "" uri)
119        Stack stack;
120        MappingRecord nn;
121        nn = new MappingRecord(EMPTYSTRING, EMPTYSTRING, -1);
122        stack = createPrefixStack(EMPTYSTRING);
123        stack.push(nn);
124
125        // define "xml" namespace
126        nn = new MappingRecord(XML_PREFIX, "http://www.w3.org/XML/1998/namespace", -1);
127        stack = createPrefixStack(XML_PREFIX);
128        stack.push(nn);
129    }
130
131    /**
132     * Use a namespace prefix to lookup a namespace URI.
133     *
134     * @param prefix String the prefix of the namespace
135     * @return the URI corresponding to the prefix, returns ""
136     * if there is no visible mapping.
137     */
138    public String lookupNamespace(String prefix)
139    {
140        String uri = null;
141        final Stack stack = getPrefixStack(prefix);
142        if (stack != null && !stack.isEmpty()) {
143            uri = ((MappingRecord) stack.peek()).m_uri;
144        }
145        if (uri == null)
146            uri = EMPTYSTRING;
147        return uri;
148    }
149
150
151    MappingRecord getMappingFromPrefix(String prefix) {
152        final Stack stack = (Stack) m_namespaces.get(prefix);
153        return stack != null && !stack.isEmpty() ?
154            ((MappingRecord) stack.peek()) : null;
155    }
156
157    /**
158     * Given a namespace uri, and the namespaces mappings for the
159     * current element, return the current prefix for that uri.
160     *
161     * @param uri the namespace URI to be search for
162     * @return an existing prefix that maps to the given URI, null if no prefix
163     * maps to the given namespace URI.
164     */
165    public String lookupPrefix(String uri)
166    {
167        String foundPrefix = null;
168        Enumeration prefixes = m_namespaces.keys();
169        while (prefixes.hasMoreElements())
170        {
171            String prefix = (String) prefixes.nextElement();
172            String uri2 = lookupNamespace(prefix);
173            if (uri2 != null && uri2.equals(uri))
174            {
175                foundPrefix = prefix;
176                break;
177            }
178        }
179        return foundPrefix;
180    }
181
182    MappingRecord getMappingFromURI(String uri)
183    {
184        MappingRecord foundMap = null;
185        Enumeration prefixes = m_namespaces.keys();
186        while (prefixes.hasMoreElements())
187        {
188            String prefix = (String) prefixes.nextElement();
189            MappingRecord map2 = getMappingFromPrefix(prefix);
190            if (map2 != null && (map2.m_uri).equals(uri))
191            {
192                foundMap = map2;
193                break;
194            }
195        }
196        return foundMap;
197    }
198
199    /**
200     * Undeclare the namespace that is currently pointed to by a given prefix
201     */
202    boolean popNamespace(String prefix)
203    {
204        // Prefixes "xml" and "xmlns" cannot be redefined
205        if (prefix.startsWith(XML_PREFIX))
206        {
207            return false;
208        }
209
210        Stack stack;
211        if ((stack = getPrefixStack(prefix)) != null)
212        {
213            stack.pop();
214            return true;
215        }
216        return false;
217    }
218
219    /**
220     * Declare a mapping of a prefix to namespace URI at the given element depth.
221     * @param prefix a String with the prefix for a qualified name
222     * @param uri a String with the uri to which the prefix is to map
223     * @param elemDepth the depth of current declaration
224     */
225    public boolean pushNamespace(String prefix, String uri, int elemDepth)
226    {
227        // Prefixes "xml" and "xmlns" cannot be redefined
228        if (prefix.startsWith(XML_PREFIX))
229        {
230            return false;
231        }
232
233        Stack stack;
234        // Get the stack that contains URIs for the specified prefix
235        if ((stack = (Stack) m_namespaces.get(prefix)) == null)
236        {
237            m_namespaces.put(prefix, stack = new Stack());
238        }
239
240        if (!stack.empty())
241        {
242            MappingRecord mr = (MappingRecord)stack.peek();
243            if (uri.equals(mr.m_uri) || elemDepth == mr.m_declarationDepth) {
244                // If the same prefix/uri mapping is already on the stack
245                // don't push this one.
246                // Or if we have a mapping at the same depth
247                // don't replace by pushing this one.
248                return false;
249            }
250        }
251        MappingRecord map = new MappingRecord(prefix,uri,elemDepth);
252        stack.push(map);
253        m_nodeStack.push(map);
254        return true;
255    }
256
257    /**
258     * Pop, or undeclare all namespace definitions that are currently
259     * declared at the given element depth, or deepter.
260     * @param elemDepth the element depth for which mappings declared at this
261     * depth or deeper will no longer be valid
262     * @param saxHandler The ContentHandler to notify of any endPrefixMapping()
263     * calls.  This parameter can be null.
264     */
265    void popNamespaces(int elemDepth, ContentHandler saxHandler)
266    {
267        while (true)
268        {
269            if (m_nodeStack.isEmpty())
270                return;
271            MappingRecord map = (MappingRecord) (m_nodeStack.peek());
272            int depth = map.m_declarationDepth;
273            if (elemDepth < 1 || map.m_declarationDepth < elemDepth)
274                break;
275            /* the depth of the declared mapping is elemDepth or deeper
276             * so get rid of it
277             */
278
279            MappingRecord nm1 = (MappingRecord) m_nodeStack.pop();
280            // pop the node from the stack
281            String prefix = map.m_prefix;
282
283            Stack prefixStack = getPrefixStack(prefix);
284            MappingRecord nm2 = (MappingRecord) prefixStack.peek();
285            if (nm1 == nm2)
286            {
287                // It would be nice to always pop() but we
288                // need to check that the prefix stack still has
289                // the node we want to get rid of. This is because
290                // the optimization of essentially this situation:
291                // <a xmlns:x="abc"><b xmlns:x="" xmlns:x="abc" /></a>
292                // will remove both mappings in <b> because the
293                // new mapping is the same as the masked one and we get
294                // <a xmlns:x="abc"><b/></a>
295                // So we are only removing xmlns:x="" or
296                // xmlns:x="abc" from the depth of element <b>
297                // when going back to <a> if in fact they have
298                // not been optimized away.
299                //
300                prefixStack.pop();
301                if (saxHandler != null)
302                {
303                    try
304                    {
305                        saxHandler.endPrefixMapping(prefix);
306                    }
307                    catch (SAXException e)
308                    {
309                        // not much we can do if they aren't willing to listen
310                    }
311                }
312            }
313
314        }
315    }
316
317    /**
318     * Generate a new namespace prefix ( ns0, ns1 ...) not used before
319     * @return String a new namespace prefix ( ns0, ns1, ns2 ...)
320     */
321    public String generateNextPrefix()
322    {
323        return "ns" + (count++);
324    }
325
326
327    /**
328     * This method makes a clone of this object.
329     *
330     */
331    public Object clone() throws CloneNotSupportedException {
332        NamespaceMappings clone = new NamespaceMappings();
333        clone.m_nodeStack = (NamespaceMappings.Stack) m_nodeStack.clone();
334        clone.count = this.count;
335        clone.m_namespaces = (Hashtable) m_namespaces.clone();
336
337        clone.count = count;
338        return clone;
339
340    }
341
342    final void reset()
343    {
344        this.count = 0;
345        this.m_namespaces.clear();
346        this.m_nodeStack.clear();
347
348        initNamespaces();
349    }
350
351    /**
352     * Just a little class that ties the 3 fields together
353     * into one object, and this simplifies the pushing
354     * and popping of namespaces to one push or one pop on
355     * one stack rather than on 3 separate stacks.
356     */
357    class MappingRecord {
358        final String m_prefix;  // the prefix
359        final String m_uri;     // the uri, possibly "" but never null
360        // the depth of the element where declartion was made
361        final int m_declarationDepth;
362        MappingRecord(String prefix, String uri, int depth) {
363            m_prefix = prefix;
364            m_uri = (uri==null)? EMPTYSTRING : uri;
365            m_declarationDepth = depth;
366        }
367    }
368
369    /**
370     * Rather than using java.util.Stack, this private class
371     * provides a minimal subset of methods and is faster
372     * because it is not thread-safe.
373     */
374    private class Stack {
375        private int top = -1;
376        private int max = 20;
377        Object[] m_stack = new Object[max];
378
379        public Object clone() throws CloneNotSupportedException {
380            NamespaceMappings.Stack clone = new NamespaceMappings.Stack();
381            clone.max = this.max;
382            clone.top = this.top;
383            clone.m_stack = new Object[clone.max];
384            for (int i=0; i <= top; i++) {
385            	// We are just copying references to immutable MappingRecord objects here
386            	// so it is OK if the clone has references to these.
387            	clone.m_stack[i] = this.m_stack[i];
388            }
389            return clone;
390        }
391
392        public Stack()
393        {
394        }
395
396        public Object push(Object o) {
397            top++;
398            if (max <= top) {
399                int newMax = 2*max + 1;
400                Object[] newArray = new Object[newMax];
401                System.arraycopy(m_stack,0, newArray, 0, max);
402                max = newMax;
403                m_stack = newArray;
404            }
405            m_stack[top] = o;
406            return o;
407        }
408
409        public Object pop() {
410            Object o;
411            if (0 <= top) {
412                o = m_stack[top];
413                // m_stack[top] = null;  do we really care?
414                top--;
415            }
416            else
417                o = null;
418            return o;
419        }
420
421        public Object peek() {
422            Object o;
423            if (0 <= top) {
424                o = m_stack[top];
425            }
426            else
427                o = null;
428            return o;
429        }
430
431        public Object peek(int idx) {
432            return m_stack[idx];
433        }
434
435        public boolean isEmpty() {
436            return (top < 0);
437        }
438        public boolean empty() {
439            return (top < 0);
440        }
441
442        public void clear() {
443            for (int i=0; i<= top; i++)
444                m_stack[i] = null;
445            top = -1;
446        }
447
448        public Object getElement(int index) {
449            return m_stack[index];
450        }
451    }
452    /**
453     * A more type-safe way to get a stack of prefix mappings
454     * from the Hashtable m_namespaces
455     * (this is the only method that does the type cast).
456     */
457
458    private Stack getPrefixStack(String prefix) {
459        Stack fs = (Stack) m_namespaces.get(prefix);
460        return fs;
461    }
462
463    /**
464     * A more type-safe way of saving stacks under the
465     * m_namespaces Hashtable.
466     */
467    private Stack createPrefixStack(String prefix)
468    {
469        Stack fs = new Stack();
470        m_namespaces.put(prefix, fs);
471        return fs;
472    }
473
474    /**
475     * Given a namespace uri, get all prefixes bound to the Namespace URI in the current scope.
476     *
477     * @param uri the namespace URI to be search for
478     * @return An array of Strings which are
479     * all prefixes bound to the namespace URI in the current scope.
480     * An array of zero elements is returned if no prefixes map to the given
481     * namespace URI.
482     */
483    public String[] lookupAllPrefixes(String uri)
484    {
485        java.util.ArrayList foundPrefixes = new java.util.ArrayList();
486        Enumeration prefixes = m_namespaces.keys();
487        while (prefixes.hasMoreElements())
488        {
489            String prefix = (String) prefixes.nextElement();
490            String uri2 = lookupNamespace(prefix);
491            if (uri2 != null && uri2.equals(uri))
492            {
493                foundPrefixes.add(prefix);
494            }
495        }
496        String[] prefixArray = new String[foundPrefixes.size()];
497        foundPrefixes.toArray(prefixArray);
498        return prefixArray;
499    }
500}
501