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     */
020    package org.sonar.api.utils.text;
021    
022    import javax.annotation.Nullable;
023    import javax.xml.stream.XMLOutputFactory;
024    import javax.xml.stream.XMLStreamException;
025    import javax.xml.stream.XMLStreamWriter;
026    import java.io.Writer;
027    
028    /**
029     * TODO document that output is UTF-8
030     */
031    public 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    }