001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.xml; 014 015import static org.apache.juneau.internal.ArrayUtils.*; 016import static org.apache.juneau.xml.XmlSerializer.*; 017import static org.apache.juneau.xml.XmlSerializerSession.ContentResult.*; 018import static org.apache.juneau.xml.XmlSerializerSession.JsonType.*; 019import static org.apache.juneau.xml.annotation.XmlFormat.*; 020 021import java.lang.reflect.*; 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.internal.*; 026import org.apache.juneau.serializer.*; 027import org.apache.juneau.transform.*; 028import org.apache.juneau.xml.annotation.*; 029 030/** 031 * Session object that lives for the duration of a single use of {@link XmlSerializer}. 032 * 033 * <p> 034 * This class is NOT thread safe. 035 * It is typically discarded after one-time use although it can be reused within the same thread. 036 */ 037@SuppressWarnings({"unchecked","rawtypes"}) 038public class XmlSerializerSession extends WriterSerializerSession { 039 040 final boolean 041 autoDetectNamespaces, 042 enableNamespaces, 043 addNamespaceUrlsToRoot, 044 addBeanTypeProperties; 045 046 Namespace 047 defaultNamespace; 048 final Namespace 049 xsNamespace; 050 051 Namespace[] namespaces = new Namespace[0]; 052 053 /** 054 * Create a new session using properties specified in the context. 055 * 056 * @param ctx 057 * The context creating this session object. 058 * The context contains all the configuration settings for this object. 059 * @param args 060 * Runtime arguments. 061 * These specify session-level information such as locale and URI context. 062 * It also include session-level properties that override the properties defined on the bean and 063 * serializer contexts. 064 */ 065 protected XmlSerializerSession(XmlSerializer ctx, SerializerSessionArgs args) { 066 super(ctx, args); 067 enableNamespaces = getProperty(XML_enableNamespaces, boolean.class, ctx.enableNamespaces); 068 autoDetectNamespaces = getProperty(XML_autoDetectNamespaces, boolean.class, ctx.autoDetectNamespaces); 069 addNamespaceUrlsToRoot = getProperty(XML_addNamespaceUrisToRoot, boolean.class, ctx.addNamespaceUrlsToRoot); 070 addBeanTypeProperties = getProperty(XML_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties); 071 namespaces = getInstanceArrayProperty(XML_namespaces, Namespace.class, ctx.namespaces); 072 defaultNamespace = findDefaultNamespace(getInstanceProperty(XML_defaultNamespace, Namespace.class, ctx.defaultNamespace)); 073 xsNamespace = getInstanceProperty(XML_xsNamespace, Namespace.class, ctx.xsNamespace); 074 } 075 076 private Namespace findDefaultNamespace(Namespace n) { 077 if (n == null) 078 return null; 079 if (n.name != null && n.uri != null) 080 return n; 081 if (n.uri == null) { 082 for (Namespace n2 : namespaces) 083 if (n2.name.equals(n.name)) 084 return n2; 085 } 086 if (n.name == null) { 087 for (Namespace n2 : namespaces) 088 if (n2.uri.equals(n.uri)) 089 return n2; 090 } 091 return n; 092 } 093 094 @Override /* Session */ 095 public ObjectMap asMap() { 096 return super.asMap() 097 .append("XmlSerializerSession", new ObjectMap() 098 .append("addBeanTypeProperties", addBeanTypeProperties) 099 .append("addNamespaceUrlsToRoot", addNamespaceUrlsToRoot) 100 .append("autoDetectNamespaces", autoDetectNamespaces) 101 .append("defaultNamespace", defaultNamespace) 102 .append("enableNamespaces", enableNamespaces) 103 .append("namespaces", namespaces) 104 .append("xsNamespace", xsNamespace) 105 ); 106 } 107 108 /* 109 * Add a namespace to this session. 110 * 111 * @param ns The namespace being added. 112 */ 113 private void addNamespace(Namespace ns) { 114 if (ns == defaultNamespace) 115 return; 116 117 for (Namespace n : namespaces) 118 if (n == ns) 119 return; 120 121 if (defaultNamespace != null && (ns.uri.equals(defaultNamespace.uri) || ns.name.equals(defaultNamespace.name))) 122 defaultNamespace = ns; 123 else 124 namespaces = append(namespaces, ns); 125 } 126 127 /** 128 * Returns the {@link XmlSerializer#XML_addBeanTypeProperties} setting value for this session. 129 * 130 * @return The {@link XmlSerializer#XML_addBeanTypeProperties} setting value for this session. 131 */ 132 @Override /* SerializerSession */ 133 protected boolean isAddBeanTypeProperties() { 134 return addBeanTypeProperties; 135 } 136 137 /** 138 * Returns <jk>true</jk> if we're serializing HTML. 139 * 140 * <p> 141 * The difference in behavior is how empty non-void elements are handled. 142 * The XML serializer will produce a collapsed tag, whereas the HTML serializer will produce a start and end tag. 143 * 144 * @return <jk>true</jk> if we're generating HTML. 145 */ 146 protected boolean isHtmlMode() { 147 return false; 148 } 149 150 /** 151 * Converts the specified output target object to an {@link XmlWriter}. 152 * 153 * @param out The output target object. 154 * @return The output target object wrapped in an {@link XmlWriter}. 155 * @throws Exception 156 */ 157 public final XmlWriter getXmlWriter(SerializerPipe out) throws Exception { 158 Object output = out.getRawOutput(); 159 if (output instanceof XmlWriter) 160 return (XmlWriter)output; 161 XmlWriter w = new XmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), getUriResolver(), enableNamespaces, defaultNamespace); 162 out.setWriter(w); 163 return w; 164 } 165 166 @Override /* Serializer */ 167 protected void doSerialize(SerializerPipe out, Object o) throws Exception { 168 if (enableNamespaces && autoDetectNamespaces) 169 findNsfMappings(o); 170 serializeAnything(getXmlWriter(out), o, getExpectedRootType(o), null, null, enableNamespaces && addNamespaceUrlsToRoot, XmlFormat.DEFAULT, false, false, null); 171 } 172 173 /** 174 * Recursively searches for the XML namespaces on the specified POJO and adds them to the serializer context object. 175 * 176 * @param o The POJO to check. 177 * @throws SerializeException 178 */ 179 protected final void findNsfMappings(Object o) throws SerializeException { 180 ClassMeta<?> aType = null; // The actual type 181 aType = push(null, o, null); 182 183 if (aType != null) { 184 Namespace ns = aType.getExtendedMeta(XmlClassMeta.class).getNamespace(); 185 if (ns != null) { 186 if (ns.uri != null) 187 addNamespace(ns); 188 else 189 ns = null; 190 } 191 } 192 193 // Handle recursion 194 if (aType != null && ! aType.isPrimitive()) { 195 196 BeanMap<?> bm = null; 197 if (aType.isBeanMap()) { 198 bm = (BeanMap<?>)o; 199 } else if (aType.isBean()) { 200 bm = toBeanMap(o); 201 } else if (aType.isDelegate()) { 202 ClassMeta<?> innerType = ((Delegate<?>)o).getClassMeta(); 203 Namespace ns = innerType.getExtendedMeta(XmlClassMeta.class).getNamespace(); 204 if (ns != null) { 205 if (ns.uri != null) 206 addNamespace(ns); 207 else 208 ns = null; 209 } 210 211 if (innerType.isBean()) { 212 for (BeanPropertyMeta bpm : innerType.getBeanMeta().getPropertyMetas()) { 213 if (bpm.canRead()) { 214 ns = bpm.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); 215 if (ns != null && ns.uri != null) 216 addNamespace(ns); 217 } 218 } 219 220 } else if (innerType.isMap()) { 221 for (Object o2 : ((Map<?,?>)o).values()) 222 findNsfMappings(o2); 223 } else if (innerType.isCollection()) { 224 for (Object o2 : ((Collection<?>)o)) 225 findNsfMappings(o2); 226 } 227 228 } else if (aType.isMap()) { 229 for (Object o2 : ((Map<?,?>)o).values()) 230 findNsfMappings(o2); 231 } else if (aType.isCollection()) { 232 for (Object o2 : ((Collection<?>)o)) 233 findNsfMappings(o2); 234 } else if (aType.isArray() && ! aType.getElementType().isPrimitive()) { 235 for (Object o2 : ((Object[])o)) 236 findNsfMappings(o2); 237 } 238 if (bm != null) { 239 for (BeanPropertyValue p : bm.getValues(isTrimNulls())) { 240 241 Namespace ns = p.getMeta().getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); 242 if (ns != null && ns.uri != null) 243 addNamespace(ns); 244 245 try { 246 findNsfMappings(p.getValue()); 247 } catch (Throwable x) { 248 // Ignore 249 } 250 } 251 } 252 } 253 254 pop(); 255 } 256 257 /** 258 * Workhorse method. 259 * 260 * @param out The writer to send the output to. 261 * @param o The object to serialize. 262 * @param eType The expected type if this is a bean property value being serialized. 263 * @param elementName The root element name. 264 * @param elementNamespace The namespace of the element. 265 * @param addNamespaceUris Flag indicating that namespace URIs need to be added. 266 * @param format The format to serialize the output to. 267 * @param isMixed We're serializing mixed content, so don't use whitespace. 268 * @param preserveWhitespace 269 * <jk>true</jk> if we're serializing {@link XmlFormat#MIXED_PWS} or {@link XmlFormat#TEXT_PWS}. 270 * @param pMeta The bean property metadata if this is a bean property being serialized. 271 * @return The same writer passed in so that calls to the writer can be chained. 272 * @throws Exception If a problem occurred trying to convert the output. 273 */ 274 protected XmlWriter serializeAnything( 275 XmlWriter out, 276 Object o, 277 ClassMeta<?> eType, 278 String elementName, 279 Namespace elementNamespace, 280 boolean addNamespaceUris, 281 XmlFormat format, 282 boolean isMixed, 283 boolean preserveWhitespace, 284 BeanPropertyMeta pMeta) throws Exception { 285 286 JsonType type = null; // The type string (e.g. <type> or <x x='type'> 287 int i = isMixed ? 0 : indent; // Current indentation 288 ClassMeta<?> aType = null; // The actual type 289 ClassMeta<?> wType = null; // The wrapped type (delegate) 290 ClassMeta<?> sType = object(); // The serialized type 291 292 aType = push(elementName, o, eType); 293 294 if (eType == null) 295 eType = object(); 296 297 // Handle recursion 298 if (aType == null) { 299 o = null; 300 aType = object(); 301 } 302 303 if (o != null) { 304 305 if (aType.isDelegate()) { 306 wType = aType; 307 eType = aType = ((Delegate<?>)o).getClassMeta(); 308 } 309 310 sType = aType; 311 312 // Swap if necessary 313 PojoSwap swap = aType.getPojoSwap(this); 314 if (swap != null) { 315 o = swap.swap(this, o); 316 sType = swap.getSwapClassMeta(this); 317 318 // If the getSwapClass() method returns Object, we need to figure out 319 // the actual type now. 320 if (sType.isObject()) 321 sType = getClassMetaForObject(o); 322 } 323 } else { 324 sType = eType.getSerializedClassMeta(this); 325 } 326 327 // Does the actual type match the expected type? 328 boolean isExpectedType = true; 329 if (o == null || ! eType.same(aType)) { 330 if (eType.isNumber()) 331 isExpectedType = aType.isNumber(); 332 else if (eType.isMap()) 333 isExpectedType = aType.isMap(); 334 else if (eType.isCollectionOrArray()) 335 isExpectedType = aType.isCollectionOrArray(); 336 else 337 isExpectedType = false; 338 } 339 340 String resolvedDictionaryName = isExpectedType ? null : aType.getDictionaryName(); 341 342 // Note that the dictionary name may be specified on the actual type or the serialized type. 343 // HTML templates will have them defined on the serialized type. 344 String dictionaryName = aType.getDictionaryName(); 345 if (dictionaryName == null) 346 dictionaryName = sType.getDictionaryName(); 347 348 // char '\0' is interpreted as null. 349 if (o != null && sType.isChar() && ((Character)o).charValue() == 0) 350 o = null; 351 352 boolean isCollapsed = false; // If 'true', this is a collection and we're not rendering the outer element. 353 boolean isRaw = (sType.isReader() || sType.isInputStream()) && o != null; 354 355 // Get the JSON type string. 356 if (o == null) { 357 type = NULL; 358 } else if (sType.isCharSequence() || sType.isChar()) { 359 type = STRING; 360 } else if (sType.isNumber()) { 361 type = NUMBER; 362 } else if (sType.isBoolean()) { 363 type = BOOLEAN; 364 } else if (sType.isMapOrBean()) { 365 isCollapsed = sType.getExtendedMeta(XmlClassMeta.class).getFormat() == COLLAPSED; 366 type = OBJECT; 367 } else if (sType.isCollectionOrArray()) { 368 isCollapsed = (format == COLLAPSED && ! addNamespaceUris); 369 type = ARRAY; 370 } else { 371 type = STRING; 372 } 373 374 if (format.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT) && type.isOneOf(NULL,STRING,NUMBER,BOOLEAN)) 375 isCollapsed = true; 376 377 // Is there a name associated with this bean? 378 if (elementName == null && dictionaryName != null) { 379 elementName = dictionaryName; 380 isExpectedType = true; 381 } 382 383 if (enableNamespaces) { 384 if (elementNamespace == null) 385 elementNamespace = sType.getExtendedMeta(XmlClassMeta.class).getNamespace(); 386 if (elementNamespace == null) 387 elementNamespace = aType.getExtendedMeta(XmlClassMeta.class).getNamespace(); 388 if (elementNamespace != null && elementNamespace.uri == null) 389 elementNamespace = null; 390 if (elementNamespace == null) 391 elementNamespace = defaultNamespace; 392 } else { 393 elementNamespace = null; 394 } 395 396 // Do we need a carriage return after the start tag? 397 boolean cr = o != null && (sType.isMapOrBean() || sType.isCollectionOrArray()) && ! isMixed; 398 399 String en = elementName; 400 if (en == null && ! isRaw) { 401 en = type.toString(); 402 type = null; 403 } 404 boolean encodeEn = elementName != null; 405 String ns = (elementNamespace == null ? null : elementNamespace.name); 406 String dns = null, elementNs = null; 407 if (enableNamespaces) { 408 dns = elementName == null && defaultNamespace != null ? defaultNamespace.name : null; 409 elementNs = elementName == null ? dns : ns; 410 if (elementName == null) 411 elementNamespace = null; 412 } 413 414 // Render the start tag. 415 if (! isCollapsed) { 416 if (en != null) { 417 out.oTag(i, elementNs, en, encodeEn); 418 if (addNamespaceUris) { 419 out.attr((String)null, "xmlns", defaultNamespace.getUri()); 420 421 for (Namespace n : namespaces) 422 out.attr("xmlns", n.getName(), n.getUri()); 423 } 424 if (! isExpectedType) { 425 if (resolvedDictionaryName != null) 426 out.attr(dns, getBeanTypePropertyName(eType), resolvedDictionaryName); 427 else if (type != null && type != STRING) 428 out.attr(dns, getBeanTypePropertyName(eType), type); 429 } 430 } else { 431 out.i(i); 432 } 433 if (o == null) { 434 if ((sType.isBoolean() || sType.isNumber()) && ! sType.isNullable()) 435 o = sType.getPrimitiveDefault(); 436 } 437 438 if (o != null && ! (sType.isMapOrBean() || en == null)) 439 out.append('>'); 440 441 if (cr && ! (sType.isMapOrBean())) 442 out.nl(i+1); 443 } 444 445 ContentResult rc = CR_ELEMENTS; 446 447 // Render the tag contents. 448 if (o != null) { 449 if (sType.isUri() || (pMeta != null && pMeta.isUri())) { 450 out.textUri(o); 451 } else if (sType.isCharSequence() || sType.isChar()) { 452 if (format == XMLTEXT) 453 out.append(o); 454 else 455 out.text(o, preserveWhitespace); 456 } else if (sType.isNumber() || sType.isBoolean()) { 457 out.append(o); 458 } else if (sType.isMap() || (wType != null && wType.isMap())) { 459 if (o instanceof BeanMap) 460 rc = serializeBeanMap(out, (BeanMap)o, elementNamespace, isCollapsed, isMixed); 461 else 462 rc = serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), isMixed); 463 } else if (sType.isBean()) { 464 rc = serializeBeanMap(out, toBeanMap(o), elementNamespace, isCollapsed, isMixed); 465 } else if (sType.isCollection() || (wType != null && wType.isCollection())) { 466 if (isCollapsed) 467 this.indent--; 468 serializeCollection(out, o, sType, eType, pMeta, isMixed); 469 if (isCollapsed) 470 this.indent++; 471 } else if (sType.isArray()) { 472 if (isCollapsed) 473 this.indent--; 474 serializeCollection(out, o, sType, eType, pMeta, isMixed); 475 if (isCollapsed) 476 this.indent++; 477 } else if (sType.isReader() || sType.isInputStream()) { 478 IOUtils.pipe(o, out); 479 } else { 480 if (format == XMLTEXT) 481 out.append(toString(o)); 482 else 483 out.text(toString(o)); 484 } 485 } 486 487 pop(); 488 489 // Render the end tag. 490 if (! isCollapsed) { 491 if (en != null) { 492 if (rc == CR_EMPTY) { 493 if (isHtmlMode()) 494 out.append('>').eTag(elementNs, en, encodeEn); 495 else 496 out.append('/').append('>'); 497 } else if (rc == CR_VOID || o == null) { 498 out.append('/').append('>'); 499 } 500 else 501 out.ie(cr && rc != CR_MIXED ? i : 0).eTag(elementNs, en, encodeEn); 502 } 503 if (! isMixed) 504 out.nl(i); 505 } 506 507 return out; 508 } 509 510 private ContentResult serializeMap(XmlWriter out, Map m, ClassMeta<?> sType, 511 ClassMeta<?> eKeyType, ClassMeta<?> eValueType, boolean isMixed) throws Exception { 512 513 m = sort(m); 514 515 ClassMeta<?> keyType = eKeyType == null ? sType.getKeyType() : eKeyType; 516 ClassMeta<?> valueType = eValueType == null ? sType.getValueType() : eValueType; 517 518 boolean hasChildren = false; 519 for (Iterator i = m.entrySet().iterator(); i.hasNext();) { 520 Map.Entry e = (Map.Entry)i.next(); 521 522 Object k = e.getKey(); 523 if (k == null) { 524 k = "\u0000"; 525 } else { 526 k = generalize(k, keyType); 527 if (isTrimStrings() && k instanceof String) 528 k = k.toString().trim(); 529 } 530 531 Object value = e.getValue(); 532 533 if (! hasChildren) { 534 hasChildren = true; 535 out.append('>').nlIf(! isMixed, indent); 536 } 537 serializeAnything(out, value, valueType, toString(k), null, false, XmlFormat.DEFAULT, isMixed, false, null); 538 } 539 return hasChildren ? CR_ELEMENTS : CR_EMPTY; 540 } 541 542 private ContentResult serializeBeanMap(XmlWriter out, BeanMap<?> m, 543 Namespace elementNs, boolean isCollapsed, boolean isMixed) throws Exception { 544 boolean hasChildren = false; 545 BeanMeta<?> bm = m.getMeta(); 546 547 List<BeanPropertyValue> lp = m.getValues(isTrimNulls()); 548 549 XmlBeanMeta xbm = bm.getExtendedMeta(XmlBeanMeta.class); 550 551 Set<String> 552 attrs = xbm.getAttrPropertyNames(), 553 elements = xbm.getElementPropertyNames(), 554 collapsedElements = xbm.getCollapsedPropertyNames(); 555 String 556 attrsProperty = xbm.getAttrsPropertyName(), 557 contentProperty = xbm.getContentPropertyName(); 558 559 XmlFormat cf = null; 560 561 Object content = null; 562 ClassMeta<?> contentType = null; 563 for (BeanPropertyValue p : lp) { 564 String n = p.getName(); 565 if (attrs.contains(n) || attrs.contains("*") || n.equals(attrsProperty)) { 566 BeanPropertyMeta pMeta = p.getMeta(); 567 if (pMeta.canRead()) { 568 ClassMeta<?> cMeta = p.getClassMeta(); 569 570 String key = p.getName(); 571 Object value = p.getValue(); 572 Throwable t = p.getThrown(); 573 if (t != null) 574 onBeanGetterException(pMeta, t); 575 576 if (canIgnoreValue(cMeta, key, value)) 577 continue; 578 579 Namespace ns = (enableNamespaces && pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() != elementNs ? pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace() : null); 580 581 if (pMeta.isUri() ) { 582 out.attrUri(ns, key, value); 583 } else if (n.equals(attrsProperty)) { 584 if (value instanceof BeanMap) { 585 BeanMap<?> bm2 = (BeanMap)value; 586 for (BeanPropertyValue p2 : bm2.getValues(true)) { 587 String key2 = p2.getName(); 588 Object value2 = p2.getValue(); 589 Throwable t2 = p2.getThrown(); 590 if (t2 != null) 591 onBeanGetterException(pMeta, t); 592 out.attr(ns, key2, value2); 593 } 594 } else /* Map */ { 595 Map m2 = (Map)value; 596 for (Map.Entry e : (Set<Map.Entry>)(m2.entrySet())) { 597 out.attr(ns, toString(e.getKey()), e.getValue()); 598 } 599 } 600 } else { 601 out.attr(ns, key, value); 602 } 603 } 604 } 605 } 606 607 boolean 608 hasContent = false, 609 preserveWhitespace = false, 610 isVoidElement = xbm.getContentFormat() == VOID; 611 612 for (BeanPropertyValue p : lp) { 613 BeanPropertyMeta pMeta = p.getMeta(); 614 if (pMeta.canRead()) { 615 ClassMeta<?> cMeta = p.getClassMeta(); 616 617 String n = p.getName(); 618 if (n.equals(contentProperty)) { 619 content = p.getValue(); 620 contentType = p.getClassMeta(); 621 hasContent = true; 622 cf = xbm.getContentFormat(); 623 if (cf.isOneOf(MIXED,MIXED_PWS,TEXT,TEXT_PWS,XMLTEXT)) 624 isMixed = true; 625 if (cf.isOneOf(MIXED_PWS, TEXT_PWS)) 626 preserveWhitespace = true; 627 if (contentType.isCollection() && ((Collection)content).isEmpty()) 628 hasContent = false; 629 else if (contentType.isArray() && Array.getLength(content) == 0) 630 hasContent = false; 631 } else if (elements.contains(n) || collapsedElements.contains(n) || elements.contains("*") || collapsedElements.contains("*") ) { 632 String key = p.getName(); 633 Object value = p.getValue(); 634 Throwable t = p.getThrown(); 635 if (t != null) 636 onBeanGetterException(pMeta, t); 637 638 if (canIgnoreValue(cMeta, key, value)) 639 continue; 640 641 if (! hasChildren) { 642 hasChildren = true; 643 out.appendIf(! isCollapsed, '>').nlIf(! isMixed, indent); 644 } 645 646 XmlBeanPropertyMeta xbpm = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); 647 serializeAnything(out, value, cMeta, key, xbpm.getNamespace(), false, xbpm.getXmlFormat(), isMixed, false, pMeta); 648 } 649 } 650 } 651 if (! hasContent) 652 return (hasChildren ? CR_ELEMENTS : isVoidElement ? CR_VOID : CR_EMPTY); 653 out.append('>').nlIf(! isMixed, indent); 654 655 // Serialize XML content. 656 if (content != null) { 657 if (contentType == null) { 658 } else if (contentType.isCollection()) { 659 Collection c = (Collection)content; 660 for (Iterator i = c.iterator(); i.hasNext();) { 661 Object value = i.next(); 662 serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null); 663 } 664 } else if (contentType.isArray()) { 665 Collection c = toList(Object[].class, content); 666 for (Iterator i = c.iterator(); i.hasNext();) { 667 Object value = i.next(); 668 serializeAnything(out, value, contentType.getElementType(), null, null, false, cf, isMixed, preserveWhitespace, null); 669 } 670 } else { 671 serializeAnything(out, content, contentType, null, null, false, cf, isMixed, preserveWhitespace, null); 672 } 673 } else { 674 if (! isTrimNulls()) { 675 if (! isMixed) 676 out.i(indent); 677 out.text(content); 678 if (! isMixed) 679 out.nl(indent); 680 } 681 } 682 return isMixed ? CR_MIXED : CR_ELEMENTS; 683 } 684 685 private XmlWriter serializeCollection(XmlWriter out, Object in, ClassMeta<?> sType, 686 ClassMeta<?> eType, BeanPropertyMeta ppMeta, boolean isMixed) throws Exception { 687 688 ClassMeta<?> seType = sType.getElementType(); 689 if (seType == null) 690 seType = object(); 691 ClassMeta<?> eeType = eType.getElementType(); 692 693 Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in)); 694 695 c = sort(c); 696 697 String type2 = null; 698 699 String eName = type2; 700 Namespace eNs = null; 701 702 if (ppMeta != null) { 703 XmlBeanPropertyMeta xbpm = ppMeta.getExtendedMeta(XmlBeanPropertyMeta.class); 704 eName = xbpm.getChildName(); 705 eNs = xbpm.getNamespace(); 706 } 707 708 for (Iterator i = c.iterator(); i.hasNext();) { 709 Object value = i.next(); 710 serializeAnything(out, value, eeType, eName, eNs, false, XmlFormat.DEFAULT, isMixed, false, null); 711 } 712 return out; 713 } 714 715 static enum JsonType { 716 STRING("string"),BOOLEAN("boolean"),NUMBER("number"),ARRAY("array"),OBJECT("object"),NULL("null"); 717 718 private final String value; 719 private JsonType(String value) { 720 this.value = value; 721 } 722 723 @Override 724 public String toString() { 725 return value; 726 } 727 728 boolean isOneOf(JsonType...types) { 729 for (JsonType type : types) 730 if (type == this) 731 return true; 732 return false; 733 } 734 } 735 736 /** 737 * Identifies what the contents were of a serialized bean. 738 */ 739 static enum ContentResult { 740 CR_VOID, // No content...append "/>" to the start tag. 741 CR_EMPTY, // No content...append "/>" to the start tag if XML, "/></end>" if HTML. 742 CR_MIXED, // Mixed content...don't add whitespace. 743 CR_ELEMENTS // Elements...use normal whitespace rules. 744 } 745}