001: package org.objectweb.jonas.tools.migration.jboss;
002:
003: import java.util.*;
004: import javax.xml.parsers.*;
005: import org.w3c.dom.*;
006:
007: /**
008: * The Transformer class contains methods for querying one or more
009: * input documents and building a new output document. Transformation
010: * tasks such as these can also be programmed in XSL and performed by
011: * an XSLT engine, however, for certain kinds of transformations it
012: * may be useful to perform them in Java by extending the Transformer
013: * class.
014: *
015: * @author Rafael H. Schloming <rhs@mit.edu>
016: **/
017:
018: public class Transformer {
019:
020: /**
021: * Interface used to filter nodes when querying an input document.
022: */
023:
024: protected interface NodeFilter {
025:
026: /**
027: * Tests the given node and returns true iff it is accepted by
028: * this NodeFilter.
029: *
030: * @param node the node to test
031: * @return true iff <var>node</var> is accepted by this NodeFilter
032: */
033:
034: boolean accept(Node node);
035: }
036:
037: /**
038: * A predefined {@link NodeFilter} that accepts all nodes.
039: */
040:
041: protected static final NodeFilter ALL = new NodeFilter() {
042: public boolean accept(Node node) {
043: return true;
044: }
045: };
046:
047: /**
048: * A predefined {@link String} containing the line seperator for
049: * this system.
050: */
051:
052: protected static final String LINE = System.getProperty(
053: "line.separator", "\n\r");
054:
055: /**
056: * A stack of nodes containing the enclosing elements for the
057: * current node being output. This stack is maintained by the
058: * {@link #open(String)} and {@link #close()} methods.
059: */
060: private Stack m_stack = new Stack();
061: /**
062: * The document being output.
063: */
064: private Document m_doc;
065: /**
066: * The current node being output.
067: */
068: private Node m_node;
069:
070: /**
071: * Constructs a new Transformer ready to output to an empty document.
072: */
073:
074: protected Transformer() {
075: try {
076: DocumentBuilder db = DocumentBuilderFactory.newInstance()
077: .newDocumentBuilder();
078: m_doc = db.newDocument();
079: } catch (ParserConfigurationException e) {
080: throw new RuntimeException(e);
081: }
082: m_node = m_doc;
083: }
084:
085: /**
086: * Returns the output document.
087: *
088: * @return the output document
089: */
090:
091: protected Document getDocument() {
092: return m_doc;
093: }
094:
095: /**
096: * Return a string containing the correct number of spaces to
097: * indent for the given level of nesting. Uses a two space indent.
098: *
099: * @param level the number of levels to indent
100: * @return a string consisting of <code>2*level</code> spaces
101: */
102:
103: private String indent(int level) {
104: StringBuffer buf = new StringBuffer();
105: for (int i = 0; i < level; i++) {
106: buf.append(" ");
107: }
108: return buf.toString();
109: }
110:
111: /**
112: * Conditionally indents if <var>indent</var> is true and it is
113: * necessry to indent. The amount to indent is determined by the
114: * current stack size adjusted by the amount specified by
115: * <var>offset</var>.
116: *
117: * @param indent a flag indicating whether to indent at all
118: * @param offset adjusts the amount to indent
119: */
120:
121: private void indent(boolean indent, int offset) {
122: int level = m_stack.size() + offset;
123: if (indent && level > 0) {
124: text(LINE + indent(level));
125: }
126: }
127:
128: /**
129: * Creates a new child element of the current node with the given
130: * name and makes the new element the current node. A text node is
131: * added with an appropriate amount of indenting whitespace before
132: * the new element is added.
133: *
134: * @param name the name of the new element
135: *
136: * @see #open(String, boolean)
137: */
138:
139: protected void open(String name) {
140: open(name, true);
141: }
142:
143: /**
144: * Creates a new child element of the current node with the given
145: * name and makes the new element the current node. If indent is
146: * true then a text node with the appropriate amount of whitespace
147: * is added before the new element is created.
148: *
149: * @param name the name of the new node
150: * @param indent a flag indicating whether or not to indent
151: */
152:
153: protected void open(String name, boolean indent) {
154: indent(indent, 0);
155: Element el = m_doc.createElement(name);
156: m_node.appendChild(el);
157: m_stack.push(m_node);
158: m_node = el;
159: }
160:
161: /**
162: * Adds a text node with the appropriate amount of whitespace and
163: * restores the previous current node.
164: *
165: * @see #close(boolean)
166: */
167:
168: protected void close() {
169: close(true);
170: }
171:
172: /**
173: * Restores the previous current node. If <var>indent</var> is
174: * true an appropriate amount of indenting whitespace is added
175: * before restoring the previous current node.
176: *
177: * @param indent a flag indicating whether or not to indent
178: */
179:
180: protected void close(boolean indent) {
181: indent(indent, -1);
182: m_node = (Node) m_stack.pop();
183: }
184:
185: /**
186: * Sets an attribute on the current element.
187: *
188: * @param name the attribute name
189: * @param value the attribute value
190: * @throws IllegalStateException if the current node is not an
191: * element
192: */
193:
194: protected void set(String name, String value) {
195: if (m_node.getNodeType() != Node.ELEMENT_NODE) {
196: throw new IllegalStateException(
197: "not currently in an element");
198: }
199: Element el = (Element) m_node;
200: el.setAttribute(name, value);
201: }
202:
203: /**
204: * Adds the given text to the current node.
205: *
206: * @param text the text to add
207: */
208:
209: protected void text(String text) {
210: m_node.appendChild(m_doc.createTextNode(text));
211: }
212:
213: /**
214: * Adds a comment to the current node.
215: *
216: * @param comment the text of the comment
217: */
218:
219: protected void comment(String comment) {
220: m_node.appendChild(m_doc.createComment(comment));
221: }
222:
223: /**
224: * Creates an element with the given name that contains a single
225: * text node with the given value and adds it as a child to the
226: * current node.
227: *
228: * @param name the name of the element
229: * @param value the value of the text node
230: */
231:
232: protected void tag(String name, String value) {
233: if (value == null) {
234: return;
235: }
236: open(name);
237: text(value);
238: close(false);
239: }
240:
241: /**
242: * Queries a node for child elements with a given name and filters
243: * the result based on a given {@link NodeFilter}.
244: *
245: * @param node the node to query
246: * @param name the name of the child elements to get
247: * @param result the collection to add the results to
248: * @param filter the result filter
249: */
250:
251: protected void get(Node node, String name, Collection result,
252: NodeFilter filter) {
253: NodeList nodes = node.getChildNodes();
254: for (int i = 0; i < nodes.getLength(); i++) {
255: Node child = nodes.item(i);
256: if (child.getNodeName().equals(name)
257: && filter.accept(child)) {
258: result.add(child);
259: }
260: }
261: }
262:
263: /**
264: * Queries the given node for all elements at a given path.
265: *
266: * @param node the node to query
267: * @param path the path
268: * @param result the collection to add results to
269: */
270:
271: protected void query(Node node, String path, Collection result) {
272: query(node, path, result, ALL);
273: }
274:
275: /**
276: * Queries the given node for all elements at a given path and
277: * filters the result.
278: *
279: * @param node the node to query
280: * @param the path
281: * @param result the collection to add results to
282: * @param filter the {@link NodeFilter} used to filter the results
283: */
284:
285: protected void query(Node node, String path, Collection result,
286: NodeFilter filter) {
287: int idx = path.indexOf('/');
288: if (idx < 0) {
289: get(node, path, result, filter);
290: return;
291: }
292:
293: String first = path.substring(0, idx);
294: String rest = path.substring(idx + 1);
295: ArrayList l = new ArrayList();
296: get(node, first, l, ALL);
297: for (int i = 0; i < l.size(); i++) {
298: Node n = (Node) l.get(i);
299: query(n, rest, result, filter);
300: }
301: }
302:
303: /**
304: * Queries a node for all descendent elements at a given path.
305: *
306: * @param node the node to query
307: * @param path the path
308: * @return the resulting list of nodes
309: */
310:
311: protected List nodes(Node node, String path) {
312: List result = new ArrayList();
313: query(node, path, result);
314: return result;
315: }
316:
317: /**
318: * Queries a node for a single descedent element at a given path.
319: *
320: * @param node the node to query
321: * @param path the path
322: * @return the node or null if none is found
323: * @throws IllegalStateException if more than one node is found
324: */
325:
326: protected Node node(Node node, String path) {
327: ArrayList result = new ArrayList();
328: query(node, path, result);
329: return singleton(result);
330: }
331:
332: /**
333: * Queries a node for a string value at a given path.
334: *
335: * @param node the node to query
336: * @param path the path
337: * @return the value or null if there is none
338: * @throws IllegalStateException if the node exists but has no
339: * value
340: */
341:
342: protected String value(Node node, String path) {
343: if (node == null) {
344: return null;
345: }
346: Node val = node(node, path + "/#text");
347: if (val == null) {
348: return null;
349: } else {
350: String result = val.getNodeValue();
351: if (result == null) {
352: throw new IllegalStateException("node has no value: "
353: + val);
354: } else {
355: return result.trim();
356: }
357: }
358: }
359:
360: protected List values(Node node, String path) {
361: List result = new ArrayList();
362: List nodes = nodes(node, path + "/#text");
363: for (int i = 0; i < nodes.size(); i++) {
364: Node nd = (Node) nodes.get(i);
365: result.add(nd.getNodeValue());
366: }
367: return result;
368: }
369:
370: protected Node singleton(Collection c) {
371: if (c.isEmpty()) {
372: return null;
373: } else if (c.size() > 1) {
374: throw new IllegalStateException("too many results: " + c);
375: } else {
376: return (Node) c.iterator().next();
377: }
378: }
379:
380: private boolean isInlinable(NodeList nodes) {
381: for (int i = 0; i < nodes.getLength(); i++) {
382: Node node = nodes.item(i);
383: switch (node.getNodeType()) {
384: case Node.TEXT_NODE:
385: case Node.ENTITY_REFERENCE_NODE:
386: continue;
387: default:
388: return false;
389: }
390: }
391: return true;
392: }
393:
394: protected boolean isEmpty(String str) {
395: return str == null || str.equals("");
396: }
397:
398: protected void rename(Collection nodes, Map substitutions) {
399: for (Iterator it = nodes.iterator(); it.hasNext();) {
400: Node node = (Node) it.next();
401: rename(node, substitutions);
402: }
403: }
404:
405: protected void rename(Node node, Map substitutions) {
406: if (node == null) {
407: return;
408: }
409: if (node.getNodeType() == Node.TEXT_NODE) {
410: String value = node.getNodeValue().trim();
411: if (!value.equals("")) {
412: text(node.getNodeValue());
413: }
414: return;
415: }
416: String name = node.getNodeName();
417: if (!substitutions.containsKey(name)) {
418: return;
419: }
420: name = (String) substitutions.get(name);
421: NodeList nodes = node.getChildNodes();
422: boolean opened = false;
423: if (!isEmpty(name)) {
424: open(name);
425: opened = true;
426: }
427: for (int i = 0; i < nodes.getLength(); i++) {
428: rename(nodes.item(i), substitutions);
429: }
430: if (opened) {
431: close(!isInlinable(nodes));
432: }
433: }
434:
435: protected static class Mapper extends LinkedHashMap {
436: Mapper copy(String element) {
437: return rename(element, element);
438: }
439:
440: Mapper rename(String from, String to) {
441: put(from, to);
442: return this ;
443: }
444:
445: Mapper remove(String element) {
446: return rename(element, "");
447: }
448: }
449:
450: }
|