1 /* 2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.beans; 26 27 import java.io.*; 28 import java.util.*; 29 import java.lang.reflect.*; 30 import java.nio.charset.Charset; 31 import java.nio.charset.CharsetEncoder; 32 import java.nio.charset.IllegalCharsetNameException; 33 import java.nio.charset.UnsupportedCharsetException; 34 35 /** 36 * The <code>XMLEncoder</code> class is a complementary alternative to 37 * the <code>ObjectOutputStream</code> and can used to generate 38 * a textual representation of a <em>JavaBean</em> in the same 39 * way that the <code>ObjectOutputStream</code> can 40 * be used to create binary representation of <code>Serializable</code> 41 * objects. For example, the following fragment can be used to create 42 * a textual representation the supplied <em>JavaBean</em> 43 * and all its properties: 44 * <pre> 45 * XMLEncoder e = new XMLEncoder( 46 * new BufferedOutputStream( 47 * new FileOutputStream("Test.xml"))); 48 * e.writeObject(new JButton("Hello, world")); 49 * e.close(); 50 * </pre> 51 * Despite the similarity of their APIs, the <code>XMLEncoder</code> 52 * class is exclusively designed for the purpose of archiving graphs 53 * of <em>JavaBean</em>s as textual representations of their public 54 * properties. Like Java source files, documents written this way 55 * have a natural immunity to changes in the implementations of the classes 56 * involved. The <code>ObjectOutputStream</code> continues to be recommended 57 * for interprocess communication and general purpose serialization. 58 * <p> 59 * The <code>XMLEncoder</code> class provides a default denotation for 60 * <em>JavaBean</em>s in which they are represented as XML documents 61 * complying with version 1.0 of the XML specification and the 62 * UTF-8 character encoding of the Unicode/ISO 10646 character set. 63 * The XML documents produced by the <code>XMLEncoder</code> class are: 64 * <ul> 65 * <li> 66 * <em>Portable and version resilient</em>: they have no dependencies 67 * on the private implementation of any class and so, like Java source 68 * files, they may be exchanged between environments which may have 69 * different versions of some of the classes and between VMs from 70 * different vendors. 71 * <li> 72 * <em>Structurally compact</em>: The <code>XMLEncoder</code> class 73 * uses a <em>redundancy elimination</em> algorithm internally so that the 74 * default values of a Bean's properties are not written to the stream. 75 * <li> 76 * <em>Fault tolerant</em>: Non-structural errors in the file, 77 * caused either by damage to the file or by API changes 78 * made to classes in an archive remain localized 79 * so that a reader can report the error and continue to load the parts 80 * of the document which were not affected by the error. 81 * </ul> 82 * <p> 83 * Below is an example of an XML archive containing 84 * some user interface components from the <em>swing</em> toolkit: 85 * <pre> 86 * <?xml version="1.0" encoding="UTF-8"?> 87 * <java version="1.0" class="java.beans.XMLDecoder"> 88 * <object class="javax.swing.JFrame"> 89 * <void property="name"> 90 * <string>frame1</string> 91 * </void> 92 * <void property="bounds"> 93 * <object class="java.awt.Rectangle"> 94 * <int>0</int> 95 * <int>0</int> 96 * <int>200</int> 97 * <int>200</int> 98 * </object> 99 * </void> 100 * <void property="contentPane"> 101 * <void method="add"> 102 * <object class="javax.swing.JButton"> 103 * <void property="label"> 104 * <string>Hello</string> 105 * </void> 106 * </object> 107 * </void> 108 * </void> 109 * <void property="visible"> 110 * <boolean>true</boolean> 111 * </void> 112 * </object> 113 * </java> 114 * </pre> 115 * The XML syntax uses the following conventions: 116 * <ul> 117 * <li> 118 * Each element represents a method call. 119 * <li> 120 * The "object" tag denotes an <em>expression</em> whose value is 121 * to be used as the argument to the enclosing element. 122 * <li> 123 * The "void" tag denotes a <em>statement</em> which will 124 * be executed, but whose result will not be used as an 125 * argument to the enclosing method. 126 * <li> 127 * Elements which contain elements use those elements as arguments, 128 * unless they have the tag: "void". 129 * <li> 130 * The name of the method is denoted by the "method" attribute. 131 * <li> 132 * XML's standard "id" and "idref" attributes are used to make 133 * references to previous expressions - so as to deal with 134 * circularities in the object graph. 135 * <li> 136 * The "class" attribute is used to specify the target of a static 137 * method or constructor explicitly; its value being the fully 138 * qualified name of the class. 139 * <li> 140 * Elements with the "void" tag are executed using 141 * the outer context as the target if no target is defined 142 * by a "class" attribute. 143 * <li> 144 * Java's String class is treated specially and is 145 * written <string>Hello, world</string> where 146 * the characters of the string are converted to bytes 147 * using the UTF-8 character encoding. 148 * </ul> 149 * <p> 150 * Although all object graphs may be written using just these three 151 * tags, the following definitions are included so that common 152 * data structures can be expressed more concisely: 153 * <ul> 154 * <li> 155 * The default method name is "new". 156 * <li> 157 * A reference to a java class is written in the form 158 * <class>javax.swing.JButton</class>. 159 * <li> 160 * Instances of the wrapper classes for Java's primitive types are written 161 * using the name of the primitive type as the tag. For example, an 162 * instance of the <code>Integer</code> class could be written: 163 * <int>123</int>. Note that the <code>XMLEncoder</code> class 164 * uses Java's reflection package in which the conversion between 165 * Java's primitive types and their associated "wrapper classes" 166 * is handled internally. The API for the <code>XMLEncoder</code> class 167 * itself deals only with <code>Object</code>s. 168 * <li> 169 * In an element representing a nullary method whose name 170 * starts with "get", the "method" attribute is replaced 171 * with a "property" attribute whose value is given by removing 172 * the "get" prefix and decapitalizing the result. 173 * <li> 174 * In an element representing a monadic method whose name 175 * starts with "set", the "method" attribute is replaced 176 * with a "property" attribute whose value is given by removing 177 * the "set" prefix and decapitalizing the result. 178 * <li> 179 * In an element representing a method named "get" taking one 180 * integer argument, the "method" attribute is replaced 181 * with an "index" attribute whose value the value of the 182 * first argument. 183 * <li> 184 * In an element representing a method named "set" taking two arguments, 185 * the first of which is an integer, the "method" attribute is replaced 186 * with an "index" attribute whose value the value of the 187 * first argument. 188 * <li> 189 * A reference to an array is written using the "array" 190 * tag. The "class" and "length" attributes specify the 191 * sub-type of the array and its length respectively. 192 * </ul> 193 * 194 *<p> 195 * For more information you might also want to check out 196 * <a 197 href="http://java.sun.com/products/jfc/tsc/articles/persistence4">Using XMLEncoder</a>, 198 * an article in <em>The Swing Connection.</em> 199 * @see XMLDecoder 200 * @see java.io.ObjectOutputStream 201 * 202 * @since 1.4 203 * 204 * @author Philip Milne 205 */ 206 public class XMLEncoder extends Encoder implements AutoCloseable { 207 208 private final CharsetEncoder encoder; 209 private final String charset; 210 private final boolean declaration; 211 212 private OutputStreamWriter out; 213 private Object owner; 214 private int indentation = 0; 215 private boolean internal = false; 216 private Map<Object, ValueData> valueToExpression; 217 private Map<Object, List<Statement>> targetToStatementList; 218 private boolean preambleWritten = false; 219 private NameGenerator nameGenerator; 220 221 private class ValueData { 222 public int refs = 0; 223 public boolean marked = false; // Marked -> refs > 0 unless ref was a target. 224 public String name = null; 225 public Expression exp = null; 226 } 227 228 /** 229 * Creates a new XML encoder to write out <em>JavaBeans</em> 230 * to the stream <code>out</code> using an XML encoding. 231 * 232 * @param out the stream to which the XML representation of 233 * the objects will be written 234 * 235 * @throws IllegalArgumentException 236 * if <code>out</code> is <code>null</code> 237 * 238 * @see XMLDecoder#XMLDecoder(InputStream) 239 */ 240 public XMLEncoder(OutputStream out) { 241 this(out, "UTF-8", true, 0); 242 } 243 244 /** 245 * Creates a new XML encoder to write out <em>JavaBeans</em> 246 * to the stream <code>out</code> using the given <code>charset</code> 247 * starting from the given <code>indentation</code>. 248 * 249 * @param out the stream to which the XML representation of 250 * the objects will be written 251 * @param charset the name of the requested charset; 252 * may be either a canonical name or an alias 253 * @param declaration whether the XML declaration should be generated; 254 * set this to <code>false</code> 255 * when embedding the contents in another XML document 256 * @param indentation the number of space characters to indent the entire XML document by 257 * 258 * @throws IllegalArgumentException 259 * if <code>out</code> or <code>charset</code> is <code>null</code>, 260 * or if <code>indentation</code> is less than 0 261 * 262 * @throws IllegalCharsetNameException 263 * if <code>charset</code> name is illegal 264 * 265 * @throws UnsupportedCharsetException 266 * if no support for the named charset is available 267 * in this instance of the Java virtual machine 268 * 269 * @throws UnsupportedOperationException 270 * if loaded charset does not support encoding 271 * 272 * @see Charset#forName(String) 273 * 274 * @since 1.7 275 */ 276 public XMLEncoder(OutputStream out, String charset, boolean declaration, int indentation) { 277 if (out == null) { 278 throw new IllegalArgumentException("the output stream cannot be null"); 279 } 280 if (indentation < 0) { 281 throw new IllegalArgumentException("the indentation must be >= 0"); 282 } 283 Charset cs = Charset.forName(charset); 284 this.encoder = cs.newEncoder(); 285 this.charset = charset; 286 this.declaration = declaration; 287 this.indentation = indentation; 288 this.out = new OutputStreamWriter(out, cs.newEncoder()); 289 valueToExpression = new IdentityHashMap<>(); 290 targetToStatementList = new IdentityHashMap<>(); 291 nameGenerator = new NameGenerator(); 292 } 293 294 /** 295 * Sets the owner of this encoder to <code>owner</code>. 296 * 297 * @param owner The owner of this encoder. 298 * 299 * @see #getOwner 300 */ 301 public void setOwner(Object owner) { 302 this.owner = owner; 303 writeExpression(new Expression(this, "getOwner", new Object[0])); 304 } 305 306 /** 307 * Gets the owner of this encoder. 308 * 309 * @return The owner of this encoder. 310 * 311 * @see #setOwner 312 */ 313 public Object getOwner() { 314 return owner; 315 } 316 317 /** 318 * Write an XML representation of the specified object to the output. 319 * 320 * @param o The object to be written to the stream. 321 * 322 * @see XMLDecoder#readObject 323 */ 324 public void writeObject(Object o) { 325 if (internal) { 326 super.writeObject(o); 327 } 328 else { 329 writeStatement(new Statement(this, "writeObject", new Object[]{o})); 330 } 331 } 332 333 private List<Statement> statementList(Object target) { 334 List<Statement> list = targetToStatementList.get(target); 335 if (list == null) { 336 list = new ArrayList<>(); 337 targetToStatementList.put(target, list); 338 } 339 return list; 340 } 341 342 343 private void mark(Object o, boolean isArgument) { 344 if (o == null || o == this) { 345 return; 346 } 347 ValueData d = getValueData(o); 348 Expression exp = d.exp; 349 // Do not mark liternal strings. Other strings, which might, 350 // for example, come from resource bundles should still be marked. 351 if (o.getClass() == String.class && exp == null) { 352 return; 353 } 354 355 // Bump the reference counts of all arguments 356 if (isArgument) { 357 d.refs++; 358 } 359 if (d.marked) { 360 return; 361 } 362 d.marked = true; 363 Object target = exp.getTarget(); 364 mark(exp); 365 if (!(target instanceof Class)) { 366 statementList(target).add(exp); 367 // Pending: Why does the reference count need to 368 // be incremented here? 369 d.refs++; 370 } 371 } 372 373 private void mark(Statement stm) { 374 Object[] args = stm.getArguments(); 375 for (int i = 0; i < args.length; i++) { 376 Object arg = args[i]; 377 mark(arg, true); 378 } 379 mark(stm.getTarget(), stm instanceof Expression); 380 } 381 382 383 /** 384 * Records the Statement so that the Encoder will 385 * produce the actual output when the stream is flushed. 386 * <P> 387 * This method should only be invoked within the context 388 * of initializing a persistence delegate. 389 * 390 * @param oldStm The statement that will be written 391 * to the stream. 392 * @see java.beans.PersistenceDelegate#initialize 393 */ 394 public void writeStatement(Statement oldStm) { 395 // System.out.println("XMLEncoder::writeStatement: " + oldStm); 396 boolean internal = this.internal; 397 this.internal = true; 398 try { 399 super.writeStatement(oldStm); 400 /* 401 Note we must do the mark first as we may 402 require the results of previous values in 403 this context for this statement. 404 Test case is: 405 os.setOwner(this); 406 os.writeObject(this); 407 */ 408 mark(oldStm); 409 Object target = oldStm.getTarget(); 410 if (target instanceof Field) { 411 String method = oldStm.getMethodName(); 412 Object[] args = oldStm.getArguments(); 413 if ((method == null) || (args == null)) { 414 } 415 else if (method.equals("get") && (args.length == 1)) { 416 target = args[0]; 417 } 418 else if (method.equals("set") && (args.length == 2)) { 419 target = args[0]; 420 } 421 } 422 statementList(target).add(oldStm); 423 } 424 catch (Exception e) { 425 getExceptionListener().exceptionThrown(new Exception("XMLEncoder: discarding statement " + oldStm, e)); 426 } 427 this.internal = internal; 428 } 429 430 431 /** 432 * Records the Expression so that the Encoder will 433 * produce the actual output when the stream is flushed. 434 * <P> 435 * This method should only be invoked within the context of 436 * initializing a persistence delegate or setting up an encoder to 437 * read from a resource bundle. 438 * <P> 439 * For more information about using resource bundles with the 440 * XMLEncoder, see 441 * http://java.sun.com/products/jfc/tsc/articles/persistence4/#i18n 442 * 443 * @param oldExp The expression that will be written 444 * to the stream. 445 * @see java.beans.PersistenceDelegate#initialize 446 */ 447 public void writeExpression(Expression oldExp) { 448 boolean internal = this.internal; 449 this.internal = true; 450 Object oldValue = getValue(oldExp); 451 if (get(oldValue) == null || (oldValue instanceof String && !internal)) { 452 getValueData(oldValue).exp = oldExp; 453 super.writeExpression(oldExp); 454 } 455 this.internal = internal; 456 } 457 458 /** 459 * This method writes out the preamble associated with the 460 * XML encoding if it has not been written already and 461 * then writes out all of the values that been 462 * written to the stream since the last time <code>flush</code> 463 * was called. After flushing, all internal references to the 464 * values that were written to this stream are cleared. 465 */ 466 public void flush() { 467 if (!preambleWritten) { // Don't do this in constructor - it throws ... pending. 468 if (this.declaration) { 469 writeln("<?xml version=" + quote("1.0") + 470 " encoding=" + quote(this.charset) + "?>"); 471 } 472 writeln("<java version=" + quote(System.getProperty("java.version")) + 473 " class=" + quote(XMLDecoder.class.getName()) + ">"); 474 preambleWritten = true; 475 } 476 indentation++; 477 List<Statement> statements = statementList(this); 478 while (!statements.isEmpty()) { 479 Statement s = statements.remove(0); 480 if ("writeObject".equals(s.getMethodName())) { 481 outputValue(s.getArguments()[0], this, true); 482 } 483 else { 484 outputStatement(s, this, false); 485 } 486 } 487 indentation--; 488 489 Statement statement = getMissedStatement(); 490 while (statement != null) { 491 outputStatement(statement, this, false); 492 statement = getMissedStatement(); 493 } 494 495 try { 496 out.flush(); 497 } 498 catch (IOException e) { 499 getExceptionListener().exceptionThrown(e); 500 } 501 clear(); 502 } 503 504 void clear() { 505 super.clear(); 506 nameGenerator.clear(); 507 valueToExpression.clear(); 508 targetToStatementList.clear(); 509 } 510 511 Statement getMissedStatement() { 512 for (List<Statement> statements : this.targetToStatementList.values()) { 513 for (int i = 0; i < statements.size(); i++) { 514 if (Statement.class == statements.get(i).getClass()) { 515 return statements.remove(i); 516 } 517 } 518 } 519 return null; 520 } 521 522 523 /** 524 * This method calls <code>flush</code>, writes the closing 525 * postamble and then closes the output stream associated 526 * with this stream. 527 */ 528 public void close() { 529 flush(); 530 writeln("</java>"); 531 try { 532 out.close(); 533 } 534 catch (IOException e) { 535 getExceptionListener().exceptionThrown(e); 536 } 537 } 538 539 private String quote(String s) { 540 return "\"" + s + "\""; 541 } 542 543 private ValueData getValueData(Object o) { 544 ValueData d = valueToExpression.get(o); 545 if (d == null) { 546 d = new ValueData(); 547 valueToExpression.put(o, d); 548 } 549 return d; 550 } 551 552 /** 553 * Returns <code>true</code> if the argument, 554 * a Unicode code point, is valid in XML documents. 555 * Unicode characters fit into the low sixteen bits of a Unicode code point, 556 * and pairs of Unicode <em>surrogate characters</em> can be combined 557 * to encode Unicode code point in documents containing only Unicode. 558 * (The <code>char</code> datatype in the Java Programming Language 559 * represents Unicode characters, including unpaired surrogates.) 560 * <par> 561 * [2] Char ::= #x0009 | #x000A | #x000D 562 * | [#x0020-#xD7FF] 563 * | [#xE000-#xFFFD] 564 * | [#x10000-#x10ffff] 565 * </par> 566 * 567 * @param code the 32-bit Unicode code point being tested 568 * @return <code>true</code> if the Unicode code point is valid, 569 * <code>false</code> otherwise 570 */ 571 private static boolean isValidCharCode(int code) { 572 return (0x0020 <= code && code <= 0xD7FF) 573 || (0x000A == code) 574 || (0x0009 == code) 575 || (0x000D == code) 576 || (0xE000 <= code && code <= 0xFFFD) 577 || (0x10000 <= code && code <= 0x10ffff); 578 } 579 580 private void writeln(String exp) { 581 try { 582 StringBuilder sb = new StringBuilder(); 583 for(int i = 0; i < indentation; i++) { 584 sb.append(' '); 585 } 586 sb.append(exp); 587 sb.append('\n'); 588 this.out.write(sb.toString()); 589 } 590 catch (IOException e) { 591 getExceptionListener().exceptionThrown(e); 592 } 593 } 594 595 private void outputValue(Object value, Object outer, boolean isArgument) { 596 if (value == null) { 597 writeln("<null/>"); 598 return; 599 } 600 601 if (value instanceof Class) { 602 writeln("<class>" + ((Class)value).getName() + "</class>"); 603 return; 604 } 605 606 ValueData d = getValueData(value); 607 if (d.exp != null) { 608 Object target = d.exp.getTarget(); 609 String methodName = d.exp.getMethodName(); 610 611 if (target == null || methodName == null) { 612 throw new NullPointerException((target == null ? "target" : 613 "methodName") + " should not be null"); 614 } 615 616 if (isArgument && target instanceof Field && methodName.equals("get")) { 617 Field f = (Field)target; 618 writeln("<object class=" + quote(f.getDeclaringClass().getName()) + 619 " field=" + quote(f.getName()) + "/>"); 620 return; 621 } 622 623 Class<?> primitiveType = primitiveTypeFor(value.getClass()); 624 if (primitiveType != null && target == value.getClass() && 625 methodName.equals("new")) { 626 String primitiveTypeName = primitiveType.getName(); 627 // Make sure that character types are quoted correctly. 628 if (primitiveType == Character.TYPE) { 629 char code = ((Character) value).charValue(); 630 if (!isValidCharCode(code)) { 631 writeln(createString(code)); 632 return; 633 } 634 value = quoteCharCode(code); 635 if (value == null) { 636 value = Character.valueOf(code); 637 } 638 } 639 writeln("<" + primitiveTypeName + ">" + value + "</" + 640 primitiveTypeName + ">"); 641 return; 642 } 643 644 } else if (value instanceof String) { 645 writeln(createString((String) value)); 646 return; 647 } 648 649 if (d.name != null) { 650 if (isArgument) { 651 writeln("<object idref=" + quote(d.name) + "/>"); 652 } 653 else { 654 outputXML("void", " idref=" + quote(d.name), value); 655 } 656 } 657 else if (d.exp != null) { 658 outputStatement(d.exp, outer, isArgument); 659 } 660 } 661 662 private static String quoteCharCode(int code) { 663 switch(code) { 664 case '&': return "&"; 665 case '<': return "<"; 666 case '>': return ">"; 667 case '"': return """; 668 case '\'': return "'"; 669 case '\r': return " "; 670 default: return null; 671 } 672 } 673 674 private static String createString(int code) { 675 return "<char code=\"#" + Integer.toString(code, 16) + "\"/>"; 676 } 677 678 private String createString(String string) { 679 StringBuilder sb = new StringBuilder(); 680 sb.append("<string>"); 681 int index = 0; 682 while (index < string.length()) { 683 int point = string.codePointAt(index); 684 int count = Character.charCount(point); 685 686 if (isValidCharCode(point) && this.encoder.canEncode(string.substring(index, index + count))) { 687 String value = quoteCharCode(point); 688 if (value != null) { 689 sb.append(value); 690 } else { 691 sb.appendCodePoint(point); 692 } 693 index += count; 694 } else { 695 sb.append(createString(string.charAt(index))); 696 index++; 697 } 698 } 699 sb.append("</string>"); 700 return sb.toString(); 701 } 702 703 private void outputStatement(Statement exp, Object outer, boolean isArgument) { 704 Object target = exp.getTarget(); 705 String methodName = exp.getMethodName(); 706 707 if (target == null || methodName == null) { 708 throw new NullPointerException((target == null ? "target" : 709 "methodName") + " should not be null"); 710 } 711 712 Object[] args = exp.getArguments(); 713 boolean expression = exp.getClass() == Expression.class; 714 Object value = (expression) ? getValue((Expression)exp) : null; 715 716 String tag = (expression && isArgument) ? "object" : "void"; 717 String attributes = ""; 718 ValueData d = getValueData(value); 719 720 // Special cases for targets. 721 if (target == outer) { 722 } 723 else if (target == Array.class && methodName.equals("newInstance")) { 724 tag = "array"; 725 attributes = attributes + " class=" + quote(((Class)args[0]).getName()); 726 attributes = attributes + " length=" + quote(args[1].toString()); 727 args = new Object[]{}; 728 } 729 else if (target.getClass() == Class.class) { 730 attributes = attributes + " class=" + quote(((Class)target).getName()); 731 } 732 else { 733 d.refs = 2; 734 if (d.name == null) { 735 getValueData(target).refs++; 736 List<Statement> statements = statementList(target); 737 if (!statements.contains(exp)) { 738 statements.add(exp); 739 } 740 outputValue(target, outer, false); 741 } 742 if (expression) { 743 outputValue(value, outer, isArgument); 744 } 745 return; 746 } 747 if (expression && (d.refs > 1)) { 748 String instanceName = nameGenerator.instanceName(value); 749 d.name = instanceName; 750 attributes = attributes + " id=" + quote(instanceName); 751 } 752 753 // Special cases for methods. 754 if ((!expression && methodName.equals("set") && args.length == 2 && 755 args[0] instanceof Integer) || 756 (expression && methodName.equals("get") && args.length == 1 && 757 args[0] instanceof Integer)) { 758 attributes = attributes + " index=" + quote(args[0].toString()); 759 args = (args.length == 1) ? new Object[]{} : new Object[]{args[1]}; 760 } 761 else if ((!expression && methodName.startsWith("set") && args.length == 1) || 762 (expression && methodName.startsWith("get") && args.length == 0)) { 763 if (3 < methodName.length()) { 764 attributes = attributes + " property=" + 765 quote(Introspector.decapitalize(methodName.substring(3))); 766 } 767 } 768 else if (!methodName.equals("new") && !methodName.equals("newInstance")) { 769 attributes = attributes + " method=" + quote(methodName); 770 } 771 outputXML(tag, attributes, value, args); 772 } 773 774 private void outputXML(String tag, String attributes, Object value, Object... args) { 775 List<Statement> statements = statementList(value); 776 // Use XML's short form when there is no body. 777 if (args.length == 0 && statements.size() == 0) { 778 writeln("<" + tag + attributes + "/>"); 779 return; 780 } 781 782 writeln("<" + tag + attributes + ">"); 783 indentation++; 784 785 for(int i = 0; i < args.length; i++) { 786 outputValue(args[i], null, true); 787 } 788 789 while (!statements.isEmpty()) { 790 Statement s = statements.remove(0); 791 outputStatement(s, value, false); 792 } 793 794 indentation--; 795 writeln("</" + tag + ">"); 796 } 797 798 @SuppressWarnings("rawtypes") 799 static Class primitiveTypeFor(Class wrapper) { 800 if (wrapper == Boolean.class) return Boolean.TYPE; 801 if (wrapper == Byte.class) return Byte.TYPE; 802 if (wrapper == Character.class) return Character.TYPE; 803 if (wrapper == Short.class) return Short.TYPE; 804 if (wrapper == Integer.class) return Integer.TYPE; 805 if (wrapper == Long.class) return Long.TYPE; 806 if (wrapper == Float.class) return Float.TYPE; 807 if (wrapper == Double.class) return Double.TYPE; 808 if (wrapper == Void.class) return Void.TYPE; 809 return null; 810 } 811 }