001/*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.utils.text;
021
022import javax.annotation.Nullable;
023import javax.xml.stream.XMLOutputFactory;
024import javax.xml.stream.XMLStreamException;
025import javax.xml.stream.XMLStreamWriter;
026import java.io.Writer;
027
028/**
029 * TODO document that output is UTF-8
030 */
031public class XmlWriter {
032
033  private final XMLStreamWriter stream;
034
035  private XmlWriter(Writer writer) {
036    try {
037      XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
038      stream = xmlFactory.createXMLStreamWriter(writer);
039    } catch (Exception e) {
040      throw new WriterException("Fail to initialize XML Stream writer", e);
041    }
042  }
043
044  public static XmlWriter of(Writer writer) {
045    return new XmlWriter(writer);
046  }
047
048  public XmlWriter declaration() {
049    try {
050      stream.writeStartDocument();
051      return this;
052    } catch (XMLStreamException e) {
053      throw rethrow(e);
054    }
055  }
056
057  public XmlWriter begin(String nodeName) {
058    try {
059      stream.writeStartElement(nodeName);
060      return this;
061    } catch (XMLStreamException e) {
062      throw rethrow(e);
063    }
064  }
065
066
067  public XmlWriter end() {
068    try {
069      stream.writeEndElement();
070      return this;
071    } catch (XMLStreamException e) {
072      throw rethrow(e);
073    }
074  }
075
076  /**
077   * Same as {@link #end()}. The parameter is unused. It's declared only to improve
078   * readability :
079   * <pre>
080   *   xml.write("rules");
081   *   xml.write("rule");
082   *   // many other writes
083   *   xml.end("rule");
084   *   xml.end("rules");
085   * </pre>
086   */
087  public XmlWriter end(String unused) {
088    return end();
089  }
090
091  public XmlWriter prop(String nodeName, @Nullable String value) {
092    if (value != null) {
093      begin(nodeName).value(value).end();
094    }
095    return this;
096  }
097
098  public XmlWriter prop(String nodeName, @Nullable Number value) {
099    if (value != null) {
100      begin(nodeName).value(value).end();
101    }
102    return this;
103  }
104
105  public XmlWriter prop(String nodeName, boolean value) {
106    return begin(nodeName).value(value).end();
107  }
108
109  public XmlWriter prop(String nodeName, long value) {
110    return begin(nodeName).value(value).end();
111  }
112
113  public XmlWriter prop(String nodeName, double value) {
114    return begin(nodeName).value(value).end();
115  }
116
117  private XmlWriter value(boolean value) {
118    try {
119      stream.writeCharacters(String.valueOf(value));
120      return this;
121    } catch (XMLStreamException e) {
122      throw rethrow(e);
123    }
124  }
125
126  private XmlWriter value(double value) {
127    try {
128      if (Double.isNaN(value) || Double.isInfinite(value)) {
129        throw new WriterException("Fail to write XML. Double value is not valid: " + value);
130      }
131      stream.writeCharacters(String.valueOf(value));
132      return this;
133    } catch (XMLStreamException e) {
134      throw rethrow(e);
135    }
136  }
137
138  private XmlWriter value(@Nullable String value) {
139    try {
140      if (value != null) {
141        stream.writeCharacters(value);
142      }
143      return this;
144    } catch (XMLStreamException e) {
145      throw rethrow(e);
146    }
147  }
148
149  private XmlWriter value(long value) {
150    try {
151      stream.writeCharacters(String.valueOf(value));
152      return this;
153    } catch (XMLStreamException e) {
154      throw rethrow(e);
155    }
156  }
157
158  private XmlWriter value(@Nullable Number value) {
159    try {
160      if (value != null) {
161        stream.writeCharacters(String.valueOf(value));
162      }
163      return this;
164    } catch (XMLStreamException e) {
165      throw rethrow(e);
166    }
167  }
168
169  public void close() {
170    try {
171      stream.writeEndDocument();
172      stream.flush();
173      stream.close();
174    } catch (XMLStreamException e) {
175      throw rethrow(e);
176    }
177  }
178
179  private IllegalStateException rethrow(XMLStreamException e) {
180    // stacktrace is not helpful
181    throw new IllegalStateException("Fail to write XML: " + e.getMessage());
182  }
183}