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