Showing the changes to java/pkg/java/beans/XMLEncoder.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,774 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2000, 2010, 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 | * <p> | ||
| 154 | * <ul> | ||
| 155 | * <li> | ||
| 156 | * The default method name is "new". | ||
| 157 | * <li> | ||
| 158 | * A reference to a java class is written in the form | ||
| 159 | * <class>javax.swing.JButton</class>. | ||
| 160 | * <li> | ||
| 161 | * Instances of the wrapper classes for Java's primitive types are written | ||
| 162 | * using the name of the primitive type as the tag. For example, an | ||
| 163 | * instance of the <code>Integer</code> class could be written: | ||
| 164 | * <int>123</int>. Note that the <code>XMLEncoder</code> class | ||
| 165 | * uses Java's reflection package in which the conversion between | ||
| 166 | * Java's primitive types and their associated "wrapper classes" | ||
| 167 | * is handled internally. The API for the <code>XMLEncoder</code> class | ||
| 168 | * itself deals only with <code>Object</code>s. | ||
| 169 | * <li> | ||
| 170 | * In an element representing a nullary method whose name | ||
| 171 | * starts with "get", the "method" attribute is replaced | ||
| 172 | * with a "property" attribute whose value is given by removing | ||
| 173 | * the "get" prefix and decapitalizing the result. | ||
| 174 | * <li> | ||
| 175 | * In an element representing a monadic method whose name | ||
| 176 | * starts with "set", the "method" attribute is replaced | ||
| 177 | * with a "property" attribute whose value is given by removing | ||
| 178 | * the "set" prefix and decapitalizing the result. | ||
| 179 | * <li> | ||
| 180 | * In an element representing a method named "get" taking one | ||
| 181 | * integer argument, the "method" attribute is replaced | ||
| 182 | * with an "index" attribute whose value the value of the | ||
| 183 | * first argument. | ||
| 184 | * <li> | ||
| 185 | * In an element representing a method named "set" taking two arguments, | ||
| 186 | * the first of which is an integer, the "method" attribute is replaced | ||
| 187 | * with an "index" attribute whose value the value of the | ||
| 188 | * first argument. | ||
| 189 | * <li> | ||
| 190 | * A reference to an array is written using the "array" | ||
| 191 | * tag. The "class" and "length" attributes specify the | ||
| 192 | * sub-type of the array and its length respectively. | ||
| 193 | * </ul> | ||
| 194 | * | ||
| 195 | *<p> | ||
| 196 | * For more information you might also want to check out | ||
| 197 | * <a | ||
| 198 | href="http://java.sun.com/products/jfc/tsc/articles/persistence4">Using XMLEncoder</a>, | ||
| 199 | * an article in <em>The Swing Connection.</em> | ||
| 200 | * @see XMLDecoder | ||
| 201 | * @see java.io.ObjectOutputStream | ||
| 202 | * | ||
| 203 | * @since 1.4 | ||
| 204 | * | ||
| 205 | * @author Philip Milne | ||
| 206 | */ | ||
| 207 | public class XMLEncoder extends Encoder implements AutoCloseable { | ||
| 208 | |||
| 209 | private final CharsetEncoder encoder; | ||
| 210 | private final String charset; | ||
| 211 | private final boolean declaration; | ||
| 212 | |||
| 213 | private OutputStreamWriter out; | ||
| 214 | private Object owner; | ||
| 215 | private int indentation = 0; | ||
| 216 | private boolean internal = false; | ||
| 217 | private Map<Object, ValueData> valueToExpression; | ||
| 218 | private Map<Object, List<Statement>> targetToStatementList; | ||
| 219 | private boolean preambleWritten = false; | ||
| 220 | private NameGenerator nameGenerator; | ||
| 221 | |||
| 222 | private class ValueData { | ||
| 223 | public int refs = 0; | ||
| 224 | public boolean marked = false; // Marked -> refs > 0 unless ref was a target. | ||
| 225 | public String name = null; | ||
| 226 | public Expression exp = null; | ||
| 227 | } | ||
| 228 | |||
| 229 | /** | ||
| 230 | * Creates a new XML encoder to write out <em>JavaBeans</em> | ||
| 231 | * to the stream <code>out</code> using an XML encoding. | ||
| 232 | * | ||
| 233 | * @param out the stream to which the XML representation of | ||
| 234 | * the objects will be written | ||
| 235 | * | ||
| 236 | * @throws IllegalArgumentException | ||
| 237 | * if <code>out</code> is <code>null</code> | ||
| 238 | * | ||
| 239 | * @see XMLDecoder#XMLDecoder(InputStream) | ||
| 240 | */ | ||
| 241 | public XMLEncoder(OutputStream out) { | ||
| 242 | this(out, "UTF-8", true, 0); | ||
| 243 | } | ||
| 244 | |||
| 245 | /** | ||
| 246 | * Creates a new XML encoder to write out <em>JavaBeans</em> | ||
| 247 | * to the stream <code>out</code> using the given <code>charset</code> | ||
| 248 | * starting from the given <code>indentation</code>. | ||
| 249 | * | ||
| 250 | * @param out the stream to which the XML representation of | ||
| 251 | * the objects will be written | ||
| 252 | * @param charset the name of the requested charset; | ||
| 253 | * may be either a canonical name or an alias | ||
| 254 | * @param declaration whether the XML declaration should be generated; | ||
| 255 | * set this to <code>false</code> | ||
| 256 | * when embedding the contents in another XML document | ||
| 257 | * @param indentation the number of space characters to indent the entire XML document by | ||
| 258 | * | ||
| 259 | * @throws IllegalArgumentException | ||
| 260 | * if <code>out</code> or <code>charset</code> is <code>null</code>, | ||
| 261 | * or if <code>indentation</code> is less than 0 | ||
| 262 | * | ||
| 263 | * @throws IllegalCharsetNameException | ||
| 264 | * if <code>charset</code> name is illegal | ||
| 265 | * | ||
| 266 | * @throws UnsupportedCharsetException | ||
| 267 | * if no support for the named charset is available | ||
| 268 | * in this instance of the Java virtual machine | ||
| 269 | * | ||
| 270 | * @throws UnsupportedOperationException | ||
| 271 | * if loaded charset does not support encoding | ||
| 272 | * | ||
| 273 | * @see Charset#forName(String) | ||
| 274 | * | ||
| 275 | * @since 1.7 | ||
| 276 | */ | ||
| 277 | public XMLEncoder(OutputStream out, String charset, boolean declaration, int indentation) { | ||
| 278 | if (out == null) { | ||
| 279 | throw new IllegalArgumentException("the output stream cannot be null"); | ||
| 280 | } | ||
| 281 | if (indentation < 0) { | ||
| 282 | throw new IllegalArgumentException("the indentation must be >= 0"); | ||
| 283 | } | ||
| 284 | Charset cs = Charset.forName(charset); | ||
| 285 | this.encoder = cs.newEncoder(); | ||
| 286 | this.charset = charset; | ||
| 287 | this.declaration = declaration; | ||
| 288 | this.indentation = indentation; | ||
| 289 | this.out = new OutputStreamWriter(out, cs.newEncoder()); | ||
| 290 | valueToExpression = new IdentityHashMap<Object, ValueData>(); | ||
| 291 | targetToStatementList = new IdentityHashMap<Object, List<Statement>>(); | ||
| 292 | nameGenerator = new NameGenerator(); | ||
| 293 | } | ||
| 294 | |||
| 295 | /** | ||
| 296 | * Sets the owner of this encoder to <code>owner</code>. | ||
| 297 | * | ||
| 298 | * @param owner The owner of this encoder. | ||
| 299 | * | ||
| 300 | * @see #getOwner | ||
| 301 | */ | ||
| 302 | public void setOwner(Object owner) { | ||
| 303 | this.owner = owner; | ||
| 304 | writeExpression(new Expression(this, "getOwner", new Object[0])); | ||
| 305 | } | ||
| 306 | |||
| 307 | /** | ||
| 308 | * Gets the owner of this encoder. | ||
| 309 | * | ||
| 310 | * @return The owner of this encoder. | ||
| 311 | * | ||
| 312 | * @see #setOwner | ||
| 313 | */ | ||
| 314 | public Object getOwner() { | ||
| 315 | return owner; | ||
| 316 | } | ||
| 317 | |||
| 318 | /** | ||
| 319 | * Write an XML representation of the specified object to the output. | ||
| 320 | * | ||
| 321 | * @param o The object to be written to the stream. | ||
| 322 | * | ||
| 323 | * @see XMLDecoder#readObject | ||
| 324 | */ | ||
| 325 | public void writeObject(Object o) { | ||
| 326 | if (internal) { | ||
| 327 | super.writeObject(o); | ||
| 328 | } | ||
| 329 | else { | ||
| 330 | writeStatement(new Statement(this, "writeObject", new Object[]{o})); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | private List<Statement> statementList(Object target) { | ||
| 335 | List<Statement> list = targetToStatementList.get(target); | ||
| 336 | if (list == null) { | ||
| 337 | list = new ArrayList<Statement>(); | ||
| 338 | targetToStatementList.put(target, list); | ||
| 339 | } | ||
| 340 | return list; | ||
| 341 | } | ||
| 342 | |||
| 343 | |||
| 344 | private void mark(Object o, boolean isArgument) { | ||
| 345 | if (o == null || o == this) { | ||
| 346 | return; | ||
| 347 | } | ||
| 348 | ValueData d = getValueData(o); | ||
| 349 | Expression exp = d.exp; | ||
| 350 | // Do not mark liternal strings. Other strings, which might, | ||
| 351 | // for example, come from resource bundles should still be marked. | ||
| 352 | if (o.getClass() == String.class && exp == null) { | ||
| 353 | return; | ||
| 354 | } | ||
| 355 | |||
| 356 | // Bump the reference counts of all arguments | ||
| 357 | if (isArgument) { | ||
| 358 | d.refs++; | ||
| 359 | } | ||
| 360 | if (d.marked) { | ||
| 361 | return; | ||
| 362 | } | ||
| 363 | d.marked = true; | ||
| 364 | Object target = exp.getTarget(); | ||
| 365 | mark(exp); | ||
| 366 | if (!(target instanceof Class)) { | ||
| 367 | statementList(target).add(exp); | ||
| 368 | // Pending: Why does the reference count need to | ||
| 369 | // be incremented here? | ||
| 370 | d.refs++; | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | private void mark(Statement stm) { | ||
| 375 | Object[] args = stm.getArguments(); | ||
| 376 | for (int i = 0; i < args.length; i++) { | ||
| 377 | Object arg = args[i]; | ||
| 378 | mark(arg, true); | ||
| 379 | } | ||
| 380 | mark(stm.getTarget(), false); | ||
| 381 | } | ||
| 382 | |||
| 383 | |||
| 384 | /** | ||
| 385 | * Records the Statement so that the Encoder will | ||
| 386 | * produce the actual output when the stream is flushed. | ||
| 387 | * <P> | ||
| 388 | * This method should only be invoked within the context | ||
| 389 | * of initializing a persistence delegate. | ||
| 390 | * | ||
| 391 | * @param oldStm The statement that will be written | ||
| 392 | * to the stream. | ||
| 393 | * @see java.beans.PersistenceDelegate#initialize | ||
| 394 | */ | ||
| 395 | public void writeStatement(Statement oldStm) { | ||
| 396 | // System.out.println("XMLEncoder::writeStatement: " + oldStm); | ||
| 397 | boolean internal = this.internal; | ||
| 398 | this.internal = true; | ||
| 399 | try { | ||
| 400 | super.writeStatement(oldStm); | ||
| 401 | /* | ||
| 402 | Note we must do the mark first as we may | ||
| 403 | require the results of previous values in | ||
| 404 | this context for this statement. | ||
| 405 | Test case is: | ||
| 406 | os.setOwner(this); | ||
| 407 | os.writeObject(this); | ||
| 408 | */ | ||
| 409 | mark(oldStm); | ||
| 410 | Object target = oldStm.getTarget(); | ||
| 411 | if (target instanceof Field) { | ||
| 412 | String method = oldStm.getMethodName(); | ||
| 413 | Object[] args = oldStm.getArguments(); | ||
| 414 | if ((method == null) || (args == null)) { | ||
| 415 | } | ||
| 416 | else if (method.equals("get") && (args.length == 1)) { | ||
| 417 | target = args[0]; | ||
| 418 | } | ||
| 419 | else if (method.equals("set") && (args.length == 2)) { | ||
| 420 | target = args[0]; | ||
| 421 | } | ||
| 422 | } | ||
| 423 | statementList(target).add(oldStm); | ||
| 424 | } | ||
| 425 | catch (Exception e) { | ||
| 426 | getExceptionListener().exceptionThrown(new Exception("XMLEncoder: discarding statement " + oldStm, e)); | ||
| 427 | } | ||
| 428 | this.internal = internal; | ||
| 429 | } | ||
| 430 | |||
| 431 | |||
| 432 | /** | ||
| 433 | * Records the Expression so that the Encoder will | ||
| 434 | * produce the actual output when the stream is flushed. | ||
| 435 | * <P> | ||
| 436 | * This method should only be invoked within the context of | ||
| 437 | * initializing a persistence delegate or setting up an encoder to | ||
| 438 | * read from a resource bundle. | ||
| 439 | * <P> | ||
| 440 | * For more information about using resource bundles with the | ||
| 441 | * XMLEncoder, see | ||
| 442 | * http://java.sun.com/products/jfc/tsc/articles/persistence4/#i18n | ||
| 443 | * | ||
| 444 | * @param oldExp The expression that will be written | ||
| 445 | * to the stream. | ||
| 446 | * @see java.beans.PersistenceDelegate#initialize | ||
| 447 | */ | ||
| 448 | public void writeExpression(Expression oldExp) { | ||
| 449 | boolean internal = this.internal; | ||
| 450 | this.internal = true; | ||
| 451 | Object oldValue = getValue(oldExp); | ||
| 452 | if (get(oldValue) == null || (oldValue instanceof String && !internal)) { | ||
| 453 | getValueData(oldValue).exp = oldExp; | ||
| 454 | super.writeExpression(oldExp); | ||
| 455 | } | ||
| 456 | this.internal = internal; | ||
| 457 | } | ||
| 458 | |||
| 459 | /** | ||
| 460 | * This method writes out the preamble associated with the | ||
| 461 | * XML encoding if it has not been written already and | ||
| 462 | * then writes out all of the values that been | ||
| 463 | * written to the stream since the last time <code>flush</code> | ||
| 464 | * was called. After flushing, all internal references to the | ||
| 465 | * values that were written to this stream are cleared. | ||
| 466 | */ | ||
| 467 | public void flush() { | ||
| 468 | if (!preambleWritten) { // Don't do this in constructor - it throws ... pending. | ||
| 469 | if (this.declaration) { | ||
| 470 | writeln("<?xml version=" + quote("1.0") + | ||
| 471 | " encoding=" + quote(this.charset) + "?>"); | ||
| 472 | } | ||
| 473 | writeln("<java version=" + quote(System.getProperty("java.version")) + | ||
| 474 | " class=" + quote(XMLDecoder.class.getName()) + ">"); | ||
| 475 | preambleWritten = true; | ||
| 476 | } | ||
| 477 | indentation++; | ||
| 478 | List<Statement> statements = statementList(this); | ||
| 479 | while (!statements.isEmpty()) { | ||
| 480 | Statement s = statements.remove(0); | ||
| 481 | if ("writeObject".equals(s.getMethodName())) { | ||
| 482 | outputValue(s.getArguments()[0], this, true); | ||
| 483 | } | ||
| 484 | else { | ||
| 485 | outputStatement(s, this, false); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | indentation--; | ||
| 489 | |||
| 490 | try { | ||
| 491 | out.flush(); | ||
| 492 | } | ||
| 493 | catch (IOException e) { | ||
| 494 | getExceptionListener().exceptionThrown(e); | ||
| 495 | } | ||
| 496 | clear(); | ||
| 497 | } | ||
| 498 | |||
| 499 | void clear() { | ||
| 500 | super.clear(); | ||
| 501 | nameGenerator.clear(); | ||
| 502 | valueToExpression.clear(); | ||
| 503 | targetToStatementList.clear(); | ||
| 504 | } | ||
| 505 | |||
| 506 | |||
| 507 | /** | ||
| 508 | * This method calls <code>flush</code>, writes the closing | ||
| 509 | * postamble and then closes the output stream associated | ||
| 510 | * with this stream. | ||
| 511 | */ | ||
| 512 | public void close() { | ||
| 513 | flush(); | ||
| 514 | writeln("</java>"); | ||
| 515 | try { | ||
| 516 | out.close(); | ||
| 517 | } | ||
| 518 | catch (IOException e) { | ||
| 519 | getExceptionListener().exceptionThrown(e); | ||
| 520 | } | ||
| 521 | } | ||
| 522 | |||
| 523 | private String quote(String s) { | ||
| 524 | return "\"" + s + "\""; | ||
| 525 | } | ||
| 526 | |||
| 527 | private ValueData getValueData(Object o) { | ||
| 528 | ValueData d = valueToExpression.get(o); | ||
| 529 | if (d == null) { | ||
| 530 | d = new ValueData(); | ||
| 531 | valueToExpression.put(o, d); | ||
| 532 | } | ||
| 533 | return d; | ||
| 534 | } | ||
| 535 | |||
| 536 | /** | ||
| 537 | * Returns <code>true</code> if the argument, | ||
| 538 | * a Unicode code point, is valid in XML documents. | ||
| 539 | * Unicode characters fit into the low sixteen bits of a Unicode code point, | ||
| 540 | * and pairs of Unicode <em>surrogate characters</em> can be combined | ||
| 541 | * to encode Unicode code point in documents containing only Unicode. | ||
| 542 | * (The <code>char</code> datatype in the Java Programming Language | ||
| 543 | * represents Unicode characters, including unpaired surrogates.) | ||
| 544 | * <par> | ||
| 545 | * [2] Char ::= #x0009 | #x000A | #x000D | ||
| 546 | * | [#x0020-#xD7FF] | ||
| 547 | * | [#xE000-#xFFFD] | ||
| 548 | * | [#x10000-#x10ffff] | ||
| 549 | * </par> | ||
| 550 | * | ||
| 551 | * @param code the 32-bit Unicode code point being tested | ||
| 552 | * @return <code>true</code> if the Unicode code point is valid, | ||
| 553 | * <code>false</code> otherwise | ||
| 554 | */ | ||
| 555 | private static boolean isValidCharCode(int code) { | ||
| 556 | return (0x0020 <= code && code <= 0xD7FF) | ||
| 557 | || (0x000A == code) | ||
| 558 | || (0x0009 == code) | ||
| 559 | || (0x000D == code) | ||
| 560 | || (0xE000 <= code && code <= 0xFFFD) | ||
| 561 | || (0x10000 <= code && code <= 0x10ffff); | ||
| 562 | } | ||
| 563 | |||
| 564 | private void writeln(String exp) { | ||
| 565 | try { | ||
| 566 | StringBuilder sb = new StringBuilder(); | ||
| 567 | for(int i = 0; i < indentation; i++) { | ||
| 568 | sb.append(' '); | ||
| 569 | } | ||
| 570 | sb.append(exp); | ||
| 571 | sb.append('\n'); | ||
| 572 | this.out.write(sb.toString()); | ||
| 573 | } | ||
| 574 | catch (IOException e) { | ||
| 575 | getExceptionListener().exceptionThrown(e); | ||
| 576 | } | ||
| 577 | } | ||
| 578 | |||
| 579 | private void outputValue(Object value, Object outer, boolean isArgument) { | ||
| 580 | if (value == null) { | ||
| 581 | writeln("<null/>"); | ||
| 582 | return; | ||
| 583 | } | ||
| 584 | |||
| 585 | if (value instanceof Class) { | ||
| 586 | writeln("<class>" + ((Class)value).getName() + "</class>"); | ||
| 587 | return; | ||
| 588 | } | ||
| 589 | |||
| 590 | ValueData d = getValueData(value); | ||
| 591 | if (d.exp != null) { | ||
| 592 | Object target = d.exp.getTarget(); | ||
| 593 | String methodName = d.exp.getMethodName(); | ||
| 594 | |||
| 595 | if (target == null || methodName == null) { | ||
| 596 | throw new NullPointerException((target == null ? "target" : | ||
| 597 | "methodName") + " should not be null"); | ||
| 598 | } | ||
| 599 | |||
| 600 | if (target instanceof Field && methodName.equals("get")) { | ||
| 601 | Field f = (Field)target; | ||
| 602 | writeln("<object class=" + quote(f.getDeclaringClass().getName()) + | ||
| 603 | " field=" + quote(f.getName()) + "/>"); | ||
| 604 | return; | ||
| 605 | } | ||
| 606 | |||
| 607 | Class primitiveType = ReflectionUtils.primitiveTypeFor(value.getClass()); | ||
| 608 | if (primitiveType != null && target == value.getClass() && | ||
| 609 | methodName.equals("new")) { | ||
| 610 | String primitiveTypeName = primitiveType.getName(); | ||
| 611 | // Make sure that character types are quoted correctly. | ||
| 612 | if (primitiveType == Character.TYPE) { | ||
| 613 | char code = ((Character) value).charValue(); | ||
| 614 | if (!isValidCharCode(code)) { | ||
| 615 | writeln(createString(code)); | ||
| 616 | return; | ||
| 617 | } | ||
| 618 | value = quoteCharCode(code); | ||
| 619 | if (value == null) { | ||
| 620 | value = Character.valueOf(code); | ||
| 621 | } | ||
| 622 | } | ||
| 623 | writeln("<" + primitiveTypeName + ">" + value + "</" + | ||
| 624 | primitiveTypeName + ">"); | ||
| 625 | return; | ||
| 626 | } | ||
| 627 | |||
| 628 | } else if (value instanceof String) { | ||
| 629 | writeln(createString((String) value)); | ||
| 630 | return; | ||
| 631 | } | ||
| 632 | |||
| 633 | if (d.name != null) { | ||
| 634 | outputXML(isArgument ? "object" : "void", " idref=" + quote(d.name), value); | ||
| 635 | } | ||
| 636 | else if (d.exp != null) { | ||
| 637 | outputStatement(d.exp, outer, isArgument); | ||
| 638 | } | ||
| 639 | } | ||
| 640 | |||
| 641 | private static String quoteCharCode(int code) { | ||
| 642 | switch(code) { | ||
| 643 | case '&': return "&"; | ||
| 644 | case '<': return "<"; | ||
| 645 | case '>': return ">"; | ||
| 646 | case '"': return """; | ||
| 647 | case '\'': return "'"; | ||
| 648 | case '\r': return " "; | ||
| 649 | default: return null; | ||
| 650 | } | ||
| 651 | } | ||
| 652 | |||
| 653 | private static String createString(int code) { | ||
| 654 | return "<char code=\"#" + Integer.toString(code, 16) + "\"/>"; | ||
| 655 | } | ||
| 656 | |||
| 657 | private String createString(String string) { | ||
| 658 | StringBuilder sb = new StringBuilder(); | ||
| 659 | sb.append("<string>"); | ||
| 660 | int index = 0; | ||
| 661 | while (index < string.length()) { | ||
| 662 | int point = string.codePointAt(index); | ||
| 663 | int count = Character.charCount(point); | ||
| 664 | |||
| 665 | if (isValidCharCode(point) && this.encoder.canEncode(string.substring(index, index + count))) { | ||
| 666 | String value = quoteCharCode(point); | ||
| 667 | if (value != null) { | ||
| 668 | sb.append(value); | ||
| 669 | } else { | ||
| 670 | sb.appendCodePoint(point); | ||
| 671 | } | ||
| 672 | index += count; | ||
| 673 | } else { | ||
| 674 | sb.append(createString(string.charAt(index))); | ||
| 675 | index++; | ||
| 676 | } | ||
| 677 | } | ||
| 678 | sb.append("</string>"); | ||
| 679 | return sb.toString(); | ||
| 680 | } | ||
| 681 | |||
| 682 | private void outputStatement(Statement exp, Object outer, boolean isArgument) { | ||
| 683 | Object target = exp.getTarget(); | ||
| 684 | String methodName = exp.getMethodName(); | ||
| 685 | |||
| 686 | if (target == null || methodName == null) { | ||
| 687 | throw new NullPointerException((target == null ? "target" : | ||
| 688 | "methodName") + " should not be null"); | ||
| 689 | } | ||
| 690 | |||
| 691 | Object[] args = exp.getArguments(); | ||
| 692 | boolean expression = exp.getClass() == Expression.class; | ||
| 693 | Object value = (expression) ? getValue((Expression)exp) : null; | ||
| 694 | |||
| 695 | String tag = (expression && isArgument) ? "object" : "void"; | ||
| 696 | String attributes = ""; | ||
| 697 | ValueData d = getValueData(value); | ||
| 698 | |||
| 699 | // Special cases for targets. | ||
| 700 | if (target == outer) { | ||
| 701 | } | ||
| 702 | else if (target == Array.class && methodName.equals("newInstance")) { | ||
| 703 | tag = "array"; | ||
| 704 | attributes = attributes + " class=" + quote(((Class)args[0]).getName()); | ||
| 705 | attributes = attributes + " length=" + quote(args[1].toString()); | ||
| 706 | args = new Object[]{}; | ||
| 707 | } | ||
| 708 | else if (target.getClass() == Class.class) { | ||
| 709 | attributes = attributes + " class=" + quote(((Class)target).getName()); | ||
| 710 | } | ||
| 711 | else { | ||
| 712 | d.refs = 2; | ||
| 713 | getValueData(target).refs++; | ||
| 714 | List<Statement> statements = statementList(target); | ||
| 715 | if (!statements.contains(exp)) { | ||
| 716 | statements.add(exp); | ||
| 717 | } | ||
| 718 | outputValue(target, outer, false); | ||
| 719 | if (expression) { | ||
| 720 | outputValue(value, outer, isArgument); | ||
| 721 | } | ||
| 722 | return; | ||
| 723 | } | ||
| 724 | if (expression && (d.refs > 1)) { | ||
| 725 | String instanceName = nameGenerator.instanceName(value); | ||
| 726 | d.name = instanceName; | ||
| 727 | attributes = attributes + " id=" + quote(instanceName); | ||
| 728 | } | ||
| 729 | |||
| 730 | // Special cases for methods. | ||
| 731 | if ((!expression && methodName.equals("set") && args.length == 2 && | ||
| 732 | args[0] instanceof Integer) || | ||
| 733 | (expression && methodName.equals("get") && args.length == 1 && | ||
| 734 | args[0] instanceof Integer)) { | ||
| 735 | attributes = attributes + " index=" + quote(args[0].toString()); | ||
| 736 | args = (args.length == 1) ? new Object[]{} : new Object[]{args[1]}; | ||
| 737 | } | ||
| 738 | else if ((!expression && methodName.startsWith("set") && args.length == 1) || | ||
| 739 | (expression && methodName.startsWith("get") && args.length == 0)) { | ||
| 740 | if (3 < methodName.length()) { | ||
| 741 | attributes = attributes + " property=" + | ||
| 742 | quote(Introspector.decapitalize(methodName.substring(3))); | ||
| 743 | } | ||
| 744 | } | ||
| 745 | else if (!methodName.equals("new") && !methodName.equals("newInstance")) { | ||
| 746 | attributes = attributes + " method=" + quote(methodName); | ||
| 747 | } | ||
| 748 | outputXML(tag, attributes, value, args); | ||
| 749 | } | ||
| 750 | |||
| 751 | private void outputXML(String tag, String attributes, Object value, Object... args) { | ||
| 752 | List<Statement> statements = statementList(value); | ||
| 753 | // Use XML's short form when there is no body. | ||
| 754 | if (args.length == 0 && statements.size() == 0) { | ||
| 755 | writeln("<" + tag + attributes + "/>"); | ||
| 756 | return; | ||
| 757 | } | ||
| 758 | |||
| 759 | writeln("<" + tag + attributes + ">"); | ||
| 760 | indentation++; | ||
| 761 | |||
| 762 | for(int i = 0; i < args.length; i++) { | ||
| 763 | outputValue(args[i], null, true); | ||
| 764 | } | ||
| 765 | |||
| 766 | while (!statements.isEmpty()) { | ||
| 767 | Statement s = statements.remove(0); | ||
| 768 | outputStatement(s, value, false); | ||
| 769 | } | ||
| 770 | |||
| 771 | indentation--; | ||
| 772 | writeln("</" + tag + ">"); | ||
| 773 | } | ||
| 774 | } | ||