001/** 002 * 003 * Copyright 2014-2017 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.util; 018 019import java.io.IOException; 020import java.io.Writer; 021import java.util.Collection; 022import java.util.Date; 023 024import org.jivesoftware.smack.packet.Element; 025import org.jivesoftware.smack.packet.ExtensionElement; 026import org.jivesoftware.smack.packet.NamedElement; 027 028import org.jxmpp.util.XmppDateTime; 029 030public class XmlStringBuilder implements Appendable, CharSequence { 031 public static final String RIGHT_ANGLE_BRACKET = Character.toString('>'); 032 033 private final LazyStringBuilder sb; 034 035 public XmlStringBuilder() { 036 sb = new LazyStringBuilder(); 037 } 038 039 public XmlStringBuilder(ExtensionElement pe) { 040 this(); 041 prelude(pe); 042 } 043 044 public XmlStringBuilder(NamedElement e) { 045 this(); 046 halfOpenElement(e.getElementName()); 047 } 048 049 public XmlStringBuilder(ExtensionElement ee, String enclosingNamespace) { 050 this(); 051 String namespace = ee.getNamespace(); 052 if (namespace.equals(enclosingNamespace)) { 053 halfOpenElement(ee.getElementName()); 054 } else { 055 prelude(ee); 056 } 057 } 058 059 public XmlStringBuilder escapedElement(String name, String escapedContent) { 060 assert escapedContent != null; 061 openElement(name); 062 append(escapedContent); 063 closeElement(name); 064 return this; 065 } 066 067 /** 068 * Add a new element to this builder. 069 * 070 * @param name 071 * @param content 072 * @return the XmlStringBuilder 073 */ 074 public XmlStringBuilder element(String name, String content) { 075 assert content != null; 076 openElement(name); 077 escape(content); 078 closeElement(name); 079 return this; 080 } 081 082 /** 083 * Add a new element to this builder, with the {@link java.util.Date} instance as its content, 084 * which will get formatted with {@link XmppDateTime#formatXEP0082Date(Date)}. 085 * 086 * @param name element name 087 * @param content content of element 088 * @return this XmlStringBuilder 089 */ 090 public XmlStringBuilder element(String name, Date content) { 091 assert content != null; 092 return element(name, XmppDateTime.formatXEP0082Date(content)); 093 } 094 095 /** 096 * Add a new element to this builder. 097 * 098 * @param name 099 * @param content 100 * @return the XmlStringBuilder 101 */ 102 public XmlStringBuilder element(String name, CharSequence content) { 103 return element(name, content.toString()); 104 } 105 106 public XmlStringBuilder element(String name, Enum<?> content) { 107 assert content != null; 108 element(name, content.name()); 109 return this; 110 } 111 112 public XmlStringBuilder element(Element element) { 113 assert element != null; 114 return append(element.toXML()); 115 } 116 117 public XmlStringBuilder optElement(String name, String content) { 118 if (content != null) { 119 element(name, content); 120 } 121 return this; 122 } 123 124 /** 125 * Add a new element to this builder, with the {@link java.util.Date} instance as its content, 126 * which will get formatted with {@link XmppDateTime#formatXEP0082Date(Date)} 127 * if {@link java.util.Date} instance is not <code>null</code>. 128 * 129 * @param name element name 130 * @param content content of element 131 * @return this XmlStringBuilder 132 */ 133 public XmlStringBuilder optElement(String name, Date content) { 134 if (content != null) { 135 element(name, content); 136 } 137 return this; 138 } 139 140 public XmlStringBuilder optElement(String name, CharSequence content) { 141 if (content != null) { 142 element(name, content.toString()); 143 } 144 return this; 145 } 146 147 public XmlStringBuilder optElement(Element element) { 148 if (element != null) { 149 append(element.toXML()); 150 } 151 return this; 152 } 153 154 public XmlStringBuilder optElement(String name, Enum<?> content) { 155 if (content != null) { 156 element(name, content); 157 } 158 return this; 159 } 160 161 public XmlStringBuilder optElement(String name, Object object) { 162 if (object != null) { 163 element(name, object.toString()); 164 } 165 return this; 166 } 167 168 public XmlStringBuilder optIntElement(String name, int value) { 169 if (value >= 0) { 170 element(name, String.valueOf(value)); 171 } 172 return this; 173 } 174 175 public XmlStringBuilder halfOpenElement(String name) { 176 assert (StringUtils.isNotEmpty(name)); 177 sb.append('<').append(name); 178 return this; 179 } 180 181 public XmlStringBuilder halfOpenElement(NamedElement namedElement) { 182 return halfOpenElement(namedElement.getElementName()); 183 } 184 185 public XmlStringBuilder openElement(String name) { 186 halfOpenElement(name).rightAngleBracket(); 187 return this; 188 } 189 190 public XmlStringBuilder closeElement(String name) { 191 sb.append("</").append(name); 192 rightAngleBracket(); 193 return this; 194 } 195 196 public XmlStringBuilder closeElement(NamedElement e) { 197 closeElement(e.getElementName()); 198 return this; 199 } 200 201 public XmlStringBuilder closeEmptyElement() { 202 sb.append("/>"); 203 return this; 204 } 205 206 /** 207 * Add a right angle bracket '>'. 208 * 209 * @return a reference to this object. 210 */ 211 public XmlStringBuilder rightAngleBracket() { 212 sb.append(RIGHT_ANGLE_BRACKET); 213 return this; 214 } 215 216 /** 217 * Add a right angle bracket '>'. 218 * 219 * @return a reference to this object 220 * @deprecated use {@link #rightAngleBracket()} instead 221 */ 222 @Deprecated 223 public XmlStringBuilder rightAngelBracket() { 224 return rightAngleBracket(); 225 } 226 227 /** 228 * Does nothing if value is null. 229 * 230 * @param name 231 * @param value 232 * @return the XmlStringBuilder 233 */ 234 public XmlStringBuilder attribute(String name, String value) { 235 assert value != null; 236 sb.append(' ').append(name).append("='"); 237 escapeAttributeValue(value); 238 sb.append('\''); 239 return this; 240 } 241 242 public XmlStringBuilder attribute(String name, boolean bool) { 243 return attribute(name, Boolean.toString(bool)); 244 } 245 246 /** 247 * Add a new attribute to this builder, with the {@link java.util.Date} instance as its value, 248 * which will get formatted with {@link XmppDateTime#formatXEP0082Date(Date)}. 249 * 250 * @param name name of attribute 251 * @param value value of attribute 252 * @return this XmlStringBuilder 253 */ 254 public XmlStringBuilder attribute(String name, Date value) { 255 assert value != null; 256 return attribute(name, XmppDateTime.formatXEP0082Date(value)); 257 } 258 259 public XmlStringBuilder attribute(String name, CharSequence value) { 260 return attribute(name, value.toString()); 261 } 262 263 public XmlStringBuilder attribute(String name, Enum<?> value) { 264 assert value != null; 265 attribute(name, value.name()); 266 return this; 267 } 268 269 public XmlStringBuilder attribute(String name, int value) { 270 assert name != null; 271 return attribute(name, String.valueOf(value)); 272 } 273 274 public XmlStringBuilder optAttribute(String name, String value) { 275 if (value != null) { 276 attribute(name, value); 277 } 278 return this; 279 } 280 281 /** 282 * Add a new attribute to this builder, with the {@link java.util.Date} instance as its value, 283 * which will get formatted with {@link XmppDateTime#formatXEP0082Date(Date)} 284 * if {@link java.util.Date} instance is not <code>null</code>. 285 * 286 * @param name attribute name 287 * @param value value of this attribute 288 * @return this XmlStringBuilder 289 */ 290 public XmlStringBuilder optAttribute(String name, Date value) { 291 if (value != null) { 292 attribute(name, value); 293 } 294 return this; 295 } 296 297 public XmlStringBuilder optAttribute(String name, CharSequence value) { 298 if (value != null) { 299 attribute(name, value.toString()); 300 } 301 return this; 302 } 303 304 public XmlStringBuilder optAttribute(String name, Enum<?> value) { 305 if (value != null) { 306 attribute(name, value.toString()); 307 } 308 return this; 309 } 310 311 /** 312 * Add the given attribute if {@code value => 0}. 313 * 314 * @param name 315 * @param value 316 * @return a reference to this object 317 */ 318 public XmlStringBuilder optIntAttribute(String name, int value) { 319 if (value >= 0) { 320 attribute(name, Integer.toString(value)); 321 } 322 return this; 323 } 324 325 /** 326 * Add the given attribute if value not null and {@code value => 0}. 327 * 328 * @param name 329 * @param value 330 * @return a reference to this object 331 */ 332 public XmlStringBuilder optLongAttribute(String name, Long value) { 333 if (value != null && value >= 0) { 334 attribute(name, Long.toString(value)); 335 } 336 return this; 337 } 338 339 public XmlStringBuilder optBooleanAttribute(String name, boolean bool) { 340 if (bool) { 341 sb.append(' ').append(name).append("='true'"); 342 } 343 return this; 344 } 345 346 public XmlStringBuilder optBooleanAttributeDefaultTrue(String name, boolean bool) { 347 if (!bool) { 348 sb.append(' ').append(name).append("='false'"); 349 } 350 return this; 351 } 352 353 public XmlStringBuilder xmlnsAttribute(String value) { 354 optAttribute("xmlns", value); 355 return this; 356 } 357 358 public XmlStringBuilder xmllangAttribute(String value) { 359 optAttribute("xml:lang", value); 360 return this; 361 } 362 363 public XmlStringBuilder optXmlLangAttribute(String lang) { 364 if (!StringUtils.isNullOrEmpty(lang)) { 365 xmllangAttribute(lang); 366 } 367 return this; 368 } 369 370 public XmlStringBuilder escape(String text) { 371 assert text != null; 372 sb.append(StringUtils.escapeForXml(text)); 373 return this; 374 } 375 376 public XmlStringBuilder escapeAttributeValue(String value) { 377 assert value != null; 378 sb.append(StringUtils.escapeForXmlAttributeApos(value)); 379 return this; 380 } 381 382 public XmlStringBuilder optEscape(CharSequence text) { 383 if (text == null) { 384 return this; 385 } 386 return escape(text); 387 } 388 389 public XmlStringBuilder escape(CharSequence text) { 390 return escape(text.toString()); 391 } 392 393 public XmlStringBuilder prelude(ExtensionElement pe) { 394 return prelude(pe.getElementName(), pe.getNamespace()); 395 } 396 397 public XmlStringBuilder prelude(String elementName, String namespace) { 398 halfOpenElement(elementName); 399 xmlnsAttribute(namespace); 400 return this; 401 } 402 403 public XmlStringBuilder optAppend(CharSequence csq) { 404 if (csq != null) { 405 append(csq); 406 } 407 return this; 408 } 409 410 public XmlStringBuilder optAppend(Element element) { 411 if (element != null) { 412 append(element.toXML()); 413 } 414 return this; 415 } 416 417 public XmlStringBuilder append(XmlStringBuilder xsb) { 418 assert xsb != null; 419 sb.append(xsb.sb); 420 return this; 421 } 422 423 public XmlStringBuilder append(Collection<? extends Element> elements) { 424 for (Element element : elements) { 425 append(element.toXML()); 426 } 427 return this; 428 } 429 430 public XmlStringBuilder emptyElement(Enum<?> element) { 431 return emptyElement(element.name()); 432 } 433 434 public XmlStringBuilder emptyElement(String element) { 435 halfOpenElement(element); 436 return closeEmptyElement(); 437 } 438 439 public XmlStringBuilder condEmptyElement(boolean condition, String element) { 440 if (condition) { 441 emptyElement(element); 442 } 443 return this; 444 } 445 446 public XmlStringBuilder condAttribute(boolean condition, String name, String value) { 447 if (condition) { 448 attribute(name, value); 449 } 450 return this; 451 } 452 453 @Override 454 public XmlStringBuilder append(CharSequence csq) { 455 assert csq != null; 456 sb.append(csq); 457 return this; 458 } 459 460 @Override 461 public XmlStringBuilder append(CharSequence csq, int start, int end) { 462 assert csq != null; 463 sb.append(csq, start, end); 464 return this; 465 } 466 467 @Override 468 public XmlStringBuilder append(char c) { 469 sb.append(c); 470 return this; 471 } 472 473 @Override 474 public int length() { 475 return sb.length(); 476 } 477 478 @Override 479 public char charAt(int index) { 480 return sb.charAt(index); 481 } 482 483 @Override 484 public CharSequence subSequence(int start, int end) { 485 return sb.subSequence(start, end); 486 } 487 488 @Override 489 public String toString() { 490 return sb.toString(); 491 } 492 493 @Override 494 public boolean equals(Object other) { 495 if (!(other instanceof CharSequence)) { 496 return false; 497 } 498 CharSequence otherCharSequenceBuilder = (CharSequence) other; 499 return toString().equals(otherCharSequenceBuilder.toString()); 500 } 501 502 @Override 503 public int hashCode() { 504 return toString().hashCode(); 505 } 506 507 /** 508 * Write the contents of this <code>XmlStringBuilder</code> to a {@link Writer}. This will write 509 * the single parts one-by-one, avoiding allocation of a big continuous memory block holding the 510 * XmlStringBuilder contents. 511 * 512 * @param writer 513 * @throws IOException 514 */ 515 public void write(Writer writer) throws IOException { 516 for (CharSequence csq : sb.getAsList()) { 517 if (csq instanceof XmlStringBuilder) { 518 ((XmlStringBuilder) csq).write(writer); 519 } 520 else { 521 writer.write(csq.toString()); 522 } 523 } 524 } 525}