001: /*
002: * Modified by Nabh Information Systems, Inc.
003: * Modifications (c) 2006 Nabh Information Systems, Inc.
004: *
005: * Copyright 2001-2004 The Apache Software Foundation.
006: *
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019: package com.nabhinc.util;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023:
024: import java.util.ArrayList;
025:
026: /**
027: * The abstraction this class provides is a push down stack of variable
028: * length frames of prefix to namespace mappings. Used for keeping track
029: * of what namespaces are active at any given point as an XML document is
030: * traversed or produced.
031: *
032: * From a performance point of view, this data will both be modified frequently
033: * (at a minimum, there will be one push and pop per XML element processed),
034: * and scanned frequently (many of the "good" mappings will be at the bottom
035: * of the stack). The one saving grace is that the expected maximum
036: * cardinalities of the number of frames and the number of total mappings
037: * is only in the dozens, representing the nesting depth of an XML document
038: * and the number of active namespaces at any point in the processing.
039: *
040: * Accordingly, this stack is implemented as a single array, will null
041: * values used to indicate frame boundaries.
042: *
043: * @author James Snell
044: * @author Glen Daniels (gdaniels@apache.org)
045: * @author Sam Ruby (rubys@us.ibm.com)
046: */
047: public class NSStack {
048: protected static Log log = LogFactory.getLog(NSStack.class
049: .getName());
050:
051: private Mapping[] stack;
052: private int top = 0;
053: private int iterator = 0;
054: private int currentDefaultNS = -1;
055: private boolean optimizePrefixes = true;
056:
057: // invariant member variable to track low-level logging requirements
058: // we cache this once per instance lifecycle to avoid repeated lookups
059: // in heavily used code.
060: private final boolean traceEnabled = log.isTraceEnabled();
061:
062: public NSStack(boolean optimizePrefixes) {
063: this .optimizePrefixes = optimizePrefixes;
064: stack = new Mapping[32];
065: stack[0] = null;
066: }
067:
068: public NSStack() {
069: stack = new Mapping[32];
070: stack[0] = null;
071: }
072:
073: /**
074: * Create a new frame at the top of the stack.
075: */
076: public void push() {
077: top++;
078:
079: if (top >= stack.length) {
080: Mapping newstack[] = new Mapping[stack.length * 2];
081: System.arraycopy(stack, 0, newstack, 0, stack.length);
082: stack = newstack;
083: }
084:
085: if (traceEnabled)
086: log.trace("NSPush (" + stack.length + ")");
087:
088: stack[top] = null;
089: }
090:
091: /**
092: * Remove the top frame from the stack.
093: */
094: public void pop() {
095: clearFrame();
096:
097: top--;
098:
099: // If we've moved below the current default NS, figure out the new
100: // default (if any)
101: if (top < currentDefaultNS) {
102: // Reset the currentDefaultNS to ignore the frame just removed.
103: currentDefaultNS = top;
104: while (currentDefaultNS > 0) {
105: if (stack[currentDefaultNS] != null
106: && stack[currentDefaultNS].getPrefix().length() == 0)
107: break;
108: currentDefaultNS--;
109: }
110: }
111:
112: if (top == 0) {
113: if (traceEnabled)
114: log.trace("NSPop (Stack Empty)");
115:
116: return;
117: }
118:
119: if (traceEnabled) {
120: log.trace("NSPop (" + stack.length + ")");
121: }
122: }
123:
124: /**
125: * Return a copy of the current frame. Returns null if none are present.
126: */
127: @SuppressWarnings("unchecked")
128: public ArrayList cloneFrame() {
129: if (stack[top] == null)
130: return null;
131:
132: ArrayList clone = new ArrayList();
133:
134: for (Mapping map = topOfFrame(); map != null; map = next()) {
135: clone.add(map);
136: }
137:
138: return clone;
139: }
140:
141: /**
142: * Remove all mappings from the current frame.
143: */
144: private void clearFrame() {
145: while (stack[top] != null)
146: top--;
147: }
148:
149: /**
150: * Reset the embedded iterator in this class to the top of the current
151: * (i.e., last) frame. Note that this is not threadsafe, nor does it
152: * provide multiple iterators, so don't use this recursively. Nor
153: * should you modify the stack while iterating over it.
154: */
155: public Mapping topOfFrame() {
156: iterator = top;
157: while (stack[iterator] != null)
158: iterator--;
159: iterator++;
160: return next();
161: }
162:
163: /**
164: * Return the next namespace mapping in the top frame.
165: */
166: public Mapping next() {
167: if (iterator > top) {
168: return null;
169: } else {
170: return stack[iterator++];
171: }
172: }
173:
174: /**
175: * Add a mapping for a namespaceURI to the specified prefix to the top
176: * frame in the stack. If the prefix is already mapped in that frame,
177: * remap it to the (possibly different) namespaceURI.
178: */
179: public void add(String namespaceURI, String prefix) {
180: int idx = top;
181: prefix = prefix.intern();
182: try {
183: // Replace duplicate prefixes (last wins - this could also fault)
184: for (int cursor = top; stack[cursor] != null; cursor--) {
185: if (stack[cursor].getPrefix() == prefix) {
186: stack[cursor].setNamespaceURI(namespaceURI);
187: idx = cursor;
188: return;
189: }
190: }
191:
192: push();
193: stack[top] = new Mapping(namespaceURI, prefix);
194: idx = top;
195: } finally {
196: // If this is the default namespace, note the new in-scope
197: // default is here.
198: if (prefix.length() == 0) {
199: currentDefaultNS = idx;
200: }
201: }
202: }
203:
204: /**
205: * Return an active prefix for the given namespaceURI. NOTE : This
206: * may return null even if the namespaceURI was actually mapped further
207: * up the stack IF the prefix which was used has been repeated further
208: * down the stack. I.e.:
209: *
210: * <pre:outer xmlns:pre="namespace">
211: * <pre:inner xmlns:pre="otherNamespace">
212: * *here's where we're looking*
213: * </pre:inner>
214: * </pre:outer>
215: *
216: * If we look for a prefix for "namespace" at the indicated spot, we won't
217: * find one because "pre" is actually mapped to "otherNamespace"
218: */
219: public String getPrefix(String namespaceURI, boolean noDefault) {
220: if ((namespaceURI == null) || (namespaceURI.length() == 0))
221: return null;
222:
223: if (optimizePrefixes) {
224: // If defaults are OK, and the given NS is the current default,
225: // return "" as the prefix to favor defaults where possible.
226: if (!noDefault
227: && currentDefaultNS > 0
228: && stack[currentDefaultNS] != null
229: && namespaceURI == stack[currentDefaultNS]
230: .getNamespaceURI())
231: return "";
232: }
233: namespaceURI = namespaceURI.intern();
234:
235: for (int cursor = top; cursor > 0; cursor--) {
236: Mapping map = stack[cursor];
237: if (map == null)
238: continue;
239:
240: if (map.getNamespaceURI() == namespaceURI) {
241: String possiblePrefix = map.getPrefix();
242: if (noDefault && possiblePrefix.length() == 0)
243: continue;
244:
245: // now make sure that this is the first occurance of this
246: // particular prefix
247: for (int cursor2 = top; true; cursor2--) {
248: if (cursor2 == cursor)
249: return possiblePrefix;
250: map = stack[cursor2];
251: if (map == null)
252: continue;
253: if (possiblePrefix == map.getPrefix())
254: break;
255: }
256: }
257: }
258:
259: return null;
260: }
261:
262: /**
263: * Return an active prefix for the given namespaceURI, including
264: * the default prefix ("").
265: */
266: public String getPrefix(String namespaceURI) {
267: return getPrefix(namespaceURI, false);
268: }
269:
270: /**
271: * Given a prefix, return the associated namespace (if any).
272: */
273: public String getNamespaceURI(String prefix) {
274: if (prefix == null)
275: prefix = "";
276:
277: prefix = prefix.intern();
278:
279: for (int cursor = top; cursor > 0; cursor--) {
280: Mapping map = stack[cursor];
281: if (map == null)
282: continue;
283:
284: if (map.getPrefix() == prefix)
285: return map.getNamespaceURI();
286: }
287:
288: return null;
289: }
290:
291: /**
292: * Produce a trace dump of the entire stack, starting from the top and
293: * including frame markers.
294: */
295: public void dump(String dumpPrefix) {
296: for (int cursor = top; cursor > 0; cursor--) {
297: Mapping map = stack[cursor];
298:
299: if (map == null) {
300: log.trace(dumpPrefix + "Null stack frame.");
301: } else {
302: log.trace(dumpPrefix + map.getNamespaceURI() + " -> "
303: + map.getPrefix());
304: }
305: }
306: }
307: }
|