File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\jenkins\util\xstream\XStreamDOM.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2012, CloudBees, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package jenkins.util.xstream;
25
26 import com.thoughtworks.xstream.XStream;
27 import com.thoughtworks.xstream.converters.Converter;
28 import com.thoughtworks.xstream.converters.ErrorWriter;
29 import com.thoughtworks.xstream.converters.MarshallingContext;
30 import com.thoughtworks.xstream.converters.UnmarshallingContext;
31 import com.thoughtworks.xstream.io.AttributeNameIterator;
32 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
33 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
34 import com.thoughtworks.xstream.io.xml.AbstractXmlReader;
35 import com.thoughtworks.xstream.io.xml.AbstractXmlWriter;
36 import com.thoughtworks.xstream.io.xml.DocumentReader;
37 import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer;
38 import com.thoughtworks.xstream.io.xml.XppDriver;
39 import hudson.Util;
40 import hudson.util.VariableResolver;
41 import hudson.util.XStream2;
42 import org.apache.commons.io.IOUtils;
43
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.io.Reader;
47 import java.io.StringReader;
48 import java.io.StringWriter;
49 import java.io.Writer;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Map.Entry;
56 import java.util.Stack;
57
58 /**
59 * XML DOM like structure to preserve a portion of XStream data as-is, so that you can
60 * process it later in a separate XStream call.
61 *
62 * <p>
63 * This object captures a subset of XML infoset that XStream understands. Namely, no XML namespace,
64 * no mixed content.
65 *
66 * <p>
67 * You use it as a field in your class (that itself participates in an XStream persistence),
68 * and have it receive the portion of that XML. Then later you can use {@link #unmarshal(XStream)}
69 * to convert this sub-tree to an object with a possibly separate XStream instance.
70 * <p>
71 * The reverse operation is {@link #from(XStream, Object)} method, which marshals an object
72 * into {@link XStreamDOM}.
73 *
74 * <p>
75 * You can also use this class to parse an entire XML document into a DOM like tree with
76 * {@link #from(HierarchicalStreamReader)} and {@link #writeTo(HierarchicalStreamWriter)}.
77 * These two methods support variants that accept other forms.
78 * <p>
79 * Whereas the above methods read from and write to {@link HierarchicalStreamReader} and,
80 * {@link HierarchicalStreamWriter}, we can also create {@link HierarchicalStreamReader}
81 * that read from DOM and {@link HierarchicalStreamWriter} that writes to DOM. See
82 * {@link #newReader()} and {@link #newWriter()} for those operations.
83 *
84 * <h3>XStreamDOM as a field of another XStream-enabled class</h3>
85 * <p>
86 * {@link XStreamDOM} can be used as a type of a field of another class that's itself XStream-enabled,
87 * such as this:
88 *
89 * <pre>
90 * class Foo {
91 * XStreamDOM bar;
92 * }
93 * </pre>
94 *
95 * With the following XML:
96 *
97 * <pre>
98 * &lt;foo>
99 * &lt;bar>
100 * &lt;payload>
101 * ...
102 * &lt;/payload>
103 * &lt;/bar>
104 * &lt;/foo>
105 * </pre>
106 *
107 * <p>
108 * The {@link XStreamDOM} object in the bar field will have the "payload" element in its tag name
109 * (which means the bar element cannot have multiple children.)
110 *
111 * <h3>XStream and name escaping</h3>
112 * <p>
113 * Because XStream wants to use letters like '$' that's not legal as a name char in XML,
114 * the XML data model that it thinks of (unescaped) is actually translated into the actual
115 * XML-compliant infoset via {@link XmlFriendlyReplacer}. This translation is done by
116 * {@link HierarchicalStreamReader} and {@link HierarchicalStreamWriter}, transparently
117 * from {@link Converter}s. In {@link XStreamDOM}, we'd like to hold the XML infoset
118 * (escaped form, in XStream speak), so in our {@link ConverterImpl} we go out of the way
119 * to cancel out this effect.
120 *
121 * @author Kohsuke Kawaguchi
122 * @since 1.473
123 */
124 public class XStreamDOM {
125 private final String tagName;
126
127 private final String[] attributes;
128
129 // one of them is non-null, the other is null
130 private final String value;
131 private final List<XStreamDOM> children;
132
133 public XStreamDOM(String tagName, Map<String, String> attributes, String value) {
134 this.tagName = tagName;
135 this.attributes = toAttributeList(attributes);
136 this.value = value;
137 this.children = null;
138 }
139
140 public XStreamDOM(String tagName, Map<String, String> attributes, List<XStreamDOM> children) {
141 this.tagName = tagName;
142 this.attributes = toAttributeList(attributes);
143 this.value = null;
144 this.children = children;
145 }
146
147 private XStreamDOM(String tagName, String[] attributes, List<XStreamDOM> children, String value) {
148 this.tagName = tagName;
149 this.attributes = attributes;
150 this.children = children;
151 this.value = value;
152 }
153
154 private String[] toAttributeList(Map<String, String> attributes) {
155 String[] r = new String[attributes.size()*2];
156 int i=0;
157 for (Entry<String, String> e : attributes.entrySet()) {
158 r[i++] = e.getKey();
159 r[i++] = e.getValue();
160 }
161 return r;
162 }
163
164 public String getTagName() {
165 return tagName;
166 }
167
168 /**
169 * Unmarshals this DOM into an object via the given XStream.
170 */
171 public <T> T unmarshal(XStream xs) {
172 return (T)xs.unmarshal(newReader());
173 }
174
175 public <T> T unmarshal(XStream xs, T root) {
176 return (T)xs.unmarshal(newReader(),root);
177 }
178
179 /**
180 * Recursively expands the variables in text and attribute values and return the new DOM.
181 *
182 * The expansion uses {@link Util#replaceMacro(String, VariableResolver)}, so any unresolved
183 * references will be left as-is.
184 */
185 public XStreamDOM expandMacro(VariableResolver<String> vars) {
186 String[] newAttributes = new String[attributes.length];
187 for (int i=0; i<attributes.length; i+=2) {
188 newAttributes[i+0] = attributes[i]; // name
189 newAttributes[i+1] = Util.replaceMacro(attributes[i+1],vars);
190 }
191
192 List<XStreamDOM> newChildren = null;
193 if (children!=null) {
194 newChildren = new ArrayList<XStreamDOM>(children.size());
195 for (XStreamDOM d : children)
196 newChildren.add(d.expandMacro(vars));
197 }
198
199 return new XStreamDOM(tagName,newAttributes,newChildren,Util.replaceMacro(value,vars));
200 }
201
202 public String getAttribute(String name) {
203 for (int i=0; i<attributes.length; i+=2)
204 if (attributes[i].equals(name))
205 return attributes[i+1];
206 return null;
207 }
208
209 public int getAttributeCount() {
210 return attributes.length / 2;
211 }
212
213 String getAttributeName(int index) {
214 return attributes[index*2];
215 }
216
217 public String getAttribute(int index) {
218 return attributes[index*2+1];
219 }
220
221 public String getValue() {
222 return value;
223 }
224
225 public List<XStreamDOM> getChildren() {
226 return children;
227 }
228
229 /**
230 * Returns a new {@link HierarchicalStreamReader} that reads a sub-tree rooted at this node.
231 */
232 public HierarchicalStreamReader newReader() {
233 return new ReaderImpl(this);
234 }
235
236 /**
237 * Returns a new {@link HierarchicalStreamWriter} for marshalling objects into {@link XStreamDOM}.
238 * After the writer receives the calls, call {@Link WriterImpl#getOutput()} to obtain the populated tree.
239 */
240 public static WriterImpl newWriter() {
241 return new WriterImpl();
242 }
243
244 /**
245 * Writes this {@link XStreamDOM} into {@link OutputStream}.
246 */
247 public void writeTo(OutputStream os) {
248 writeTo(new XppDriver().createWriter(os));
249 }
250
251 public void writeTo(Writer w) {
252 writeTo(new XppDriver().createWriter(w));
253 }
254
255 public void writeTo(HierarchicalStreamWriter w) {
256 new ConverterImpl().marshal(this,w,null);
257 }
258
259 /**
260 * Marshals the given object with the given XStream into {@link XStreamDOM} and return it.
261 */
262 public static XStreamDOM from(XStream xs, Object obj) {
263 WriterImpl w = newWriter();
264 xs.marshal(obj, w);
265 return w.getOutput();
266 }
267
268 public static XStreamDOM from(InputStream in) {
269 return from(new XppDriver().createReader(in));
270 }
271
272 public static XStreamDOM from(Reader in) {
273 return from(new XppDriver().createReader(in));
274 }
275
276 public static XStreamDOM from(HierarchicalStreamReader in) {
277 return new ConverterImpl().unmarshalElement(in, null);
278 }
279
280 public Map<String, String> getAttributeMap() {
281 Map<String,String> r = new HashMap<String, String>();
282 for (int i=0; i<attributes.length; i+=2)
283 r.put(attributes[i],attributes[i+1]);
284 return r;
285 }
286
287 private static class ReaderImpl extends AbstractXmlReader implements DocumentReader {
288 private static class Pointer {
289 final XStreamDOM node;
290 int pos;
291
292 private Pointer(XStreamDOM node) {
293 this.node = node;
294 }
295
296 public String peekNextChild() {
297 if (hasMoreChildren())
298 return node.children.get(pos).tagName;
299 return null;
300 }
301
302 public boolean hasMoreChildren() {
303 return node.children!=null && pos<node.children.size();
304 }
305
306 public String xpath() {
307 XStreamDOM child = node.children.get(pos - 1);
308 int count =0;
309 for (int i=0; i<pos-1; i++)
310 if (node.children.get(i).tagName.equals(child.tagName))
311 count++;
312 boolean more = false;
313 for (int i=pos; !more && i<node.children.size(); i++)
314 if (node.children.get(i).tagName.equals(child.tagName))
315 more = true;
316
317 if (count==0 && !more) return child.tagName; // sole child
318 return child.tagName+'['+count+']';
319 }
320 }
321
322 private final Stack<Pointer> pointers = new Stack<Pointer>();
323
324
325 public ReaderImpl(XStreamDOM current) {
326 super(new XmlFriendlyReplacer());
327 pointers.push(new Pointer(current));
328 }
329
330 private Pointer current() {
331 return pointers.peek();
332 }
333
334 public Object getCurrent() {
335 return current().node;
336 }
337
338 public boolean hasMoreChildren() {
339 return current().hasMoreChildren();
340 }
341
342 public HierarchicalStreamReader underlyingReader() {
343 return this;
344 }
345
346 public void moveDown() {
347 Pointer p = current();
348 pointers.push(new Pointer(p.node.children.get(p.pos++)));
349 }
350
351 public void moveUp() {
352 pointers.pop();
353 }
354
355 public Iterator getAttributeNames() {
356 return new AttributeNameIterator(this);
357 }
358
359 public void appendErrors(ErrorWriter errorWriter) {
360 StringBuilder buf = new StringBuilder();
361 Pointer parent = null;
362 for (Pointer cur : pointers) {
363 if (parent!=null) {
364 buf.append('/').append(parent.xpath());
365 } else {
366 buf.append(cur.node.tagName);
367 }
368 parent = cur;
369 }
370 errorWriter.add("xpath", buf.toString());
371 }
372
373 public void close() {
374 }
375
376 public String peekNextChild() {
377 return current().peekNextChild();
378 }
379
380 public String getNodeName() {
381 return unescapeXmlName(current().node.tagName);
382 }
383
384 public String getValue() {
385 return Util.fixNull(current().node.value);
386 }
387
388 public String getAttribute(String name) {
389 return current().node.getAttribute(name);
390 }
391
392 public String getAttribute(int index) {
393 return current().node.getAttribute(index);
394 }
395
396 public int getAttributeCount() {
397 return current().node.getAttributeCount();
398 }
399
400 public String getAttributeName(int index) {
401 return unescapeXmlName(current().node.getAttributeName(index));
402 }
403 }
404
405 public static class WriterImpl extends AbstractXmlWriter {
406 private static class Pending {
407 final String tagName;
408 List<XStreamDOM> children;
409 List<String> attributes = new ArrayList<String>();
410 String value;
411
412 private Pending(String tagName) {
413 this.tagName = tagName;
414 }
415
416 void addChild(XStreamDOM dom) {
417 if (children==null)
418 children = new ArrayList<XStreamDOM>();
419 children.add(dom);
420 }
421
422 XStreamDOM toDOM() {
423 return new XStreamDOM(tagName,attributes.toArray(new String[attributes.size()]),children,value);
424 }
425 }
426
427 private final Stack<Pending> pendings = new Stack<Pending>();
428
429 public WriterImpl() {
430 pendings.push(new Pending(null)); // to get the final result
431 }
432
433 public void startNode(String name) {
434 pendings.push(new Pending(escapeXmlName(name)));
435 }
436
437
438
439 public void endNode() {
440 XStreamDOM dom = pendings.pop().toDOM();
441 pendings.peek().addChild(dom);
442 }
443
444 public void addAttribute(String name, String value) {
445 List<String> atts = pendings.peek().attributes;
446 atts.add(escapeXmlName(name));
447 atts.add(value);
448 }
449
450 public void setValue(String text) {
451 pendings.peek().value = text;
452 }
453
454 public void flush() {
455 }
456
457 public void close() {
458 }
459
460 public HierarchicalStreamWriter underlyingWriter() {
461 return this;
462 }
463
464 public XStreamDOM getOutput() {
465 if (pendings.size()!=1) throw new IllegalStateException();
466 return pendings.peek().children.get(0);
467 }
468 }
469
470 public static class ConverterImpl implements Converter {
471 public boolean canConvert(Class type) {
472 return type==XStreamDOM.class;
473 }
474
475 /**
476 * {@link XStreamDOM} holds infoset (which is 'escaped' from XStream's PoV),
477 * whereas {@link HierarchicalStreamWriter} expects unescaped names,
478 * so we need to unescape it first before calling into {@link HierarchicalStreamWriter}.
479 */
480 // TODO: ideally we'd like to use the contextual HierarchicalStreamWriter to unescape,
481 // but this object isn't exposed to us
482 private String unescape(String s) {
483 return REPLACER.unescapeName(s);
484 }
485
486 private String escape(String s) {
487 return REPLACER.escapeName(s);
488 }
489
490 public void marshal(Object source, HierarchicalStreamWriter w, MarshallingContext context) {
491 XStreamDOM dom = (XStreamDOM)source;
492 w.startNode(unescape(dom.tagName));
493 for (int i=0; i<dom.attributes.length; i+=2)
494 w.addAttribute(unescape(dom.attributes[i]),dom.attributes[i+1]);
495 if (dom.value!=null)
496 w.setValue(dom.value);
497 else {
498 for (XStreamDOM c : Util.fixNull(dom.children)) {
499 marshal(c, w, context);
500 }
501 }
502 w.endNode();
503 }
504
505 /**
506 * Unmarshals a single child element.
507 */
508 public XStreamDOM unmarshal(HierarchicalStreamReader r, UnmarshallingContext context) {
509 r.moveDown();
510 XStreamDOM dom = unmarshalElement(r,context);
511 r.moveUp();
512 return dom;
513 }
514
515 public XStreamDOM unmarshalElement(HierarchicalStreamReader r, UnmarshallingContext context) {
516 String name = escape(r.getNodeName());
517
518 int c = r.getAttributeCount();
519 String[] attributes = new String[c*2];
520 for (int i=0; i<c; i++) {
521 attributes[i*2] = escape(r.getAttributeName(i));
522 attributes[i*2+1] = r.getAttribute(i);
523 }
524
525 List<XStreamDOM> children = null;
526 String value = null;
527 if (r.hasMoreChildren()) {
528 children = new ArrayList<XStreamDOM>();
529 while (r.hasMoreChildren()) {
530 children.add(unmarshal(r, context));
531 }
532 } else {
533 value = r.getValue();
534 }
535
536 return new XStreamDOM(name,attributes,children,value);
537 }
538 }
539
540 public static XmlFriendlyReplacer REPLACER = new XmlFriendlyReplacer();
541 }
542