1 /* 2 * Copyright (c) 2000, 2015, 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} class is a complementary alternative to 37 * the {@code ObjectOutputStream} and can used to generate 38 * a textual representation of a <em>JavaBean</em> in the same 39 * way that the {@code ObjectOutputStream} can 40 * be used to create binary representation of {@code Serializable} 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} 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} continues to be recommended 57 * for interprocess communication and general purpose serialization. 58 * <p> 59 * The {@code XMLEncoder} 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} 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} 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} class could be written: 163 * <int>123</int>. Note that the {@code XMLEncoder} 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} class 167 * itself deals only with {@code Object}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} 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} is {@code null} 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} using the given {@code charset} 247 * starting from the given {@code indentation}. 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} 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} or {@code charset} is {@code null}, 260 * or if {@code indentation} is less than 0 261 * 262 * @throws IllegalCharsetNameException 263 * if {@code charset} 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}. 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} 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}, 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} 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} 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} if the Unicode code point is valid, 569 * {@code false} 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 if (Modifier.isStatic(f.getModifiers())) { 619 writeln("<object class=" + quote(f.getDeclaringClass().getName()) + 620 " field=" + quote(f.getName()) + "/>"); 621 return; 622 } 623 } 624 625 Class<?> primitiveType = primitiveTypeFor(value.getClass()); 626 if (primitiveType != null && target == value.getClass() && 627 methodName.equals("new")) { 628 String primitiveTypeName = primitiveType.getName(); 629 // Make sure that character types are quoted correctly. 630 if (primitiveType == Character.TYPE) { 631 char code = ((Character) value).charValue(); 632 if (!isValidCharCode(code)) { 633 writeln(createString(code)); 634 return; 635 } 636 value = quoteCharCode(code); 637 if (value == null) { 638 value = Character.valueOf(code); 639 } 640 } 641 writeln("<" + primitiveTypeName + ">" + value + "</" + 642 primitiveTypeName + ">"); 643 return; 644 } 645 646 } else if (value instanceof String) { 647 writeln(createString((String) value)); 648 return; 649 } 650 651 if (d.name != null) { 652 if (isArgument) { 653 writeln("<object idref=" + quote(d.name) + "/>"); 654 } 655 else { 656 outputXML("void", " idref=" + quote(d.name), value); 657 } 658 } 659 else if (d.exp != null) { 660 outputStatement(d.exp, outer, isArgument); 661 } 662 } 663 664 private static String quoteCharCode(int code) { 665 switch(code) { 666 case '&': return "&"; 667 case '<': return "<"; 668 case '>': return ">"; 669 case '"': return """; 670 case '\'': return "'"; 671 case '\r': return " "; 672 default: return null; 673 } 674 } 675 676 private static String createString(int code) { 677 return "<char code=\"#" + Integer.toString(code, 16) + "\"/>"; 678 } 679 680 private String createString(String string) { 681 StringBuilder sb = new StringBuilder(); 682 sb.append("<string>"); 683 int index = 0; 684 while (index < string.length()) { 685 int point = string.codePointAt(index); 686 int count = Character.charCount(point); 687 688 if (isValidCharCode(point) && this.encoder.canEncode(string.substring(index, index + count))) { 689 String value = quoteCharCode(point); 690 if (value != null) { 691 sb.append(value); 692 } else { 693 sb.appendCodePoint(point); 694 } 695 index += count; 696 } else { 697 sb.append(createString(string.charAt(index))); 698 index++; 699 } 700 } 701 sb.append("</string>"); 702 return sb.toString(); 703 } 704 705 private void outputStatement(Statement exp, Object outer, boolean isArgument) { 706 Object target = exp.getTarget(); 707 String methodName = exp.getMethodName(); 708 709 if (target == null || methodName == null) { 710 throw new NullPointerException((target == null ? "target" : 711 "methodName") + " should not be null"); 712 } 713 714 Object[] args = exp.getArguments(); 715 boolean expression = exp.getClass() == Expression.class; 716 Object value = (expression) ? getValue((Expression)exp) : null; 717 718 String tag = (expression && isArgument) ? "object" : "void"; 719 String attributes = ""; 720 ValueData d = getValueData(value); 721 722 // Special cases for targets. 723 if (target == outer) { 724 } 725 else if (target == Array.class && methodName.equals("newInstance")) { 726 tag = "array"; 727 attributes = attributes + " class=" + quote(((Class)args[0]).getName()); 728 attributes = attributes + " length=" + quote(args[1].toString()); 729 args = new Object[]{}; 730 } 731 else if (target.getClass() == Class.class) { 732 attributes = attributes + " class=" + quote(((Class)target).getName()); 733 } 734 else { 735 d.refs = 2; 736 if (d.name == null) { 737 getValueData(target).refs++; 738 List<Statement> statements = statementList(target); 739 if (!statements.contains(exp)) { 740 statements.add(exp); 741 } 742 outputValue(target, outer, false); 743 } 744 if (expression) { 745 outputValue(value, outer, isArgument); 746 } 747 return; 748 } 749 if (expression && (d.refs > 1)) { 750 String instanceName = nameGenerator.instanceName(value); 751 d.name = instanceName; 752 attributes = attributes + " id=" + quote(instanceName); 753 } 754 755 // Special cases for methods. 756 if ((!expression && methodName.equals("set") && args.length == 2 && 757 args[0] instanceof Integer) || 758 (expression && methodName.equals("get") && args.length == 1 && 759 args[0] instanceof Integer)) { 760 attributes = attributes + " index=" + quote(args[0].toString()); 761 args = (args.length == 1) ? new Object[]{} : new Object[]{args[1]}; 762 } 763 else if ((!expression && methodName.startsWith("set") && args.length == 1) || 764 (expression && methodName.startsWith("get") && args.length == 0)) { 765 if (3 < methodName.length()) { 766 attributes = attributes + " property=" + 767 quote(Introspector.decapitalize(methodName.substring(3))); 768 } 769 } 770 else if (!methodName.equals("new") && !methodName.equals("newInstance")) { 771 attributes = attributes + " method=" + quote(methodName); 772 } 773 outputXML(tag, attributes, value, args); 774 } 775 776 private void outputXML(String tag, String attributes, Object value, Object... args) { 777 List<Statement> statements = statementList(value); 778 // Use XML's short form when there is no body. 779 if (args.length == 0 && statements.size() == 0) { 780 writeln("<" + tag + attributes + "/>"); 781 return; 782 } 783 784 writeln("<" + tag + attributes + ">"); 785 indentation++; 786 787 for(int i = 0; i < args.length; i++) { 788 outputValue(args[i], null, true); 789 } 790 791 while (!statements.isEmpty()) { 792 Statement s = statements.remove(0); 793 outputStatement(s, value, false); 794 } 795 796 indentation--; 797 writeln("</" + tag + ">"); 798 } 799 800 @SuppressWarnings("rawtypes") 801 static Class primitiveTypeFor(Class wrapper) { 802 if (wrapper == Boolean.class) return Boolean.TYPE; 803 if (wrapper == Byte.class) return Byte.TYPE; 804 if (wrapper == Character.class) return Character.TYPE; 805 if (wrapper == Short.class) return Short.TYPE; 806 if (wrapper == Integer.class) return Integer.TYPE; 807 if (wrapper == Long.class) return Long.TYPE; 808 if (wrapper == Float.class) return Float.TYPE; 809 if (wrapper == Double.class) return Double.TYPE; 810 if (wrapper == Void.class) return Void.TYPE; 811 return null; 812 } 813 }