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