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 '&gt;'.
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 '&gt;'.
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}