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 org.sonar.api.utils.DateUtils;
023
024 import javax.annotation.Nullable;
025 import java.io.Writer;
026 import java.util.Date;
027
028 /**
029 * Writes JSON as a stream. This class allows plugins to not directly depend
030 * on the underlying JSON library.
031 *
032 * <h3>How to use</h3>
033 * <pre>
034 * StringWriter json = new StringWriter();
035 * JsonWriter writer = JsonWriter.of(json);
036 * writer
037 * .beginObject()
038 * .prop("aBoolean", true)
039 * .prop("aInt", 123)
040 * .prop("aString", "foo")
041 * .beginObject().name("aList")
042 * .beginArray()
043 * .beginObject().prop("key", "ABC").endObject()
044 * .beginObject().prop("key", "DEF").endObject()
045 * .endArray()
046 * .endObject()
047 * .close();
048 * </pre>
049 *
050 * @since 4.2
051 */
052 public class JsonWriter {
053
054 private final com.google.gson.stream.JsonWriter stream;
055
056 private JsonWriter(Writer writer) {
057 this.stream = new com.google.gson.stream.JsonWriter(writer);
058 this.stream.setSerializeNulls(false);
059 this.stream.setLenient(false);
060 }
061
062 // for unit testing
063 JsonWriter(com.google.gson.stream.JsonWriter stream) {
064 this.stream = stream;
065 }
066
067 public static JsonWriter of(Writer writer) {
068 return new JsonWriter(writer);
069 }
070
071 /**
072 * Begins encoding a new array. Each call to this method must be paired with
073 * a call to {@link #endArray}. Output is <code>[</code>.
074 *
075 * @throws org.sonar.api.utils.text.WriterException on any failure
076 */
077 public JsonWriter beginArray() {
078 try {
079 stream.beginArray();
080 return this;
081 } catch (Exception e) {
082 throw rethrow(e);
083 }
084 }
085
086 /**
087 * Ends encoding the current array. Output is <code>]</code>.
088 *
089 * @throws org.sonar.api.utils.text.WriterException on any failure
090 */
091 public JsonWriter endArray() {
092 try {
093 stream.endArray();
094 return this;
095 } catch (Exception e) {
096 throw rethrow(e);
097 }
098 }
099
100 /**
101 * Begins encoding a new object. Each call to this method must be paired
102 * with a call to {@link #endObject}. Output is <code>{</code>.
103 *
104 * @throws org.sonar.api.utils.text.WriterException on any failure
105 */
106 public JsonWriter beginObject() {
107 try {
108 stream.beginObject();
109 return this;
110 } catch (Exception e) {
111 throw rethrow(e);
112 }
113 }
114
115 /**
116 * Ends encoding the current object. Output is <code>}</code>.
117 *
118 * @throws org.sonar.api.utils.text.WriterException on any failure
119 */
120 public JsonWriter endObject() {
121 try {
122 stream.endObject();
123 return this;
124 } catch (Exception e) {
125 throw rethrow(e);
126 }
127 }
128
129 /**
130 * Encodes the property name. Output is <code>"theName":</code>.
131 *
132 * @throws org.sonar.api.utils.text.WriterException on any failure
133 */
134 public JsonWriter name(String name) {
135 try {
136 stream.name(name);
137 return this;
138 } catch (Exception e) {
139 throw rethrow(e);
140 }
141 }
142
143 /**
144 * Encodes {@code value}. Output is <code>true</code> or <code>false</code>.
145 *
146 * @throws org.sonar.api.utils.text.WriterException on any failure
147 */
148 public JsonWriter value(boolean value) {
149 try {
150 stream.value(value);
151 return this;
152 } catch (Exception e) {
153 throw rethrow(e);
154 }
155 }
156
157 /**
158 * @throws org.sonar.api.utils.text.WriterException on any failure
159 */
160 public JsonWriter value(double value) {
161 try {
162 stream.value(value);
163 return this;
164 } catch (Exception e) {
165 throw rethrow(e);
166 }
167 }
168
169 /**
170 * @throws org.sonar.api.utils.text.WriterException on any failure
171 */
172 public JsonWriter value(@Nullable String value) {
173 try {
174 stream.value(value);
175 return this;
176 } catch (Exception e) {
177 throw rethrow(e);
178 }
179 }
180
181 /**
182 * @throws org.sonar.api.utils.text.WriterException on any failure
183 */
184 public JsonWriter valueDate(@Nullable Date value) {
185 try {
186 stream.value(value == null ? null : DateUtils.formatDate(value));
187 return this;
188 } catch (Exception e) {
189 throw rethrow(e);
190 }
191 }
192
193 public JsonWriter valueDateTime(@Nullable Date value) {
194 try {
195 stream.value(value == null ? null : DateUtils.formatDateTime(value));
196 return this;
197 } catch (Exception e) {
198 throw rethrow(e);
199 }
200 }
201
202 /**
203 * @throws org.sonar.api.utils.text.WriterException on any failure
204 */
205 public JsonWriter value(long value) {
206 try {
207 stream.value(value);
208 return this;
209 } catch (Exception e) {
210 throw rethrow(e);
211 }
212 }
213
214 /**
215 * @throws org.sonar.api.utils.text.WriterException on any failure
216 */
217 public JsonWriter value(@Nullable Number value) {
218 try {
219 stream.value(value);
220 return this;
221 } catch (Exception e) {
222 throw rethrow(e);
223 }
224 }
225
226 /**
227 * Encodes the property name and value. Output is for example <code>"theName":123</code>.
228 *
229 * @throws org.sonar.api.utils.text.WriterException on any failure
230 */
231 public JsonWriter prop(String name, @Nullable Number value) {
232 return name(name).value(value);
233 }
234
235 /**
236 * Encodes the property name and date value (ISO format).
237 * Output is for example <code>"theDate":"2013-01-24"</code>.
238 *
239 * @throws org.sonar.api.utils.text.WriterException on any failure
240 */
241 public JsonWriter propDate(String name, @Nullable Date value) {
242 return name(name).valueDate(value);
243 }
244
245 /**
246 * Encodes the property name and datetime value (ISO format).
247 * Output is for example <code>"theDate":"2013-01-24T13:12:45+01"</code>.
248 *
249 * @throws org.sonar.api.utils.text.WriterException on any failure
250 */
251 public JsonWriter propDateTime(String name, @Nullable Date value) {
252 return name(name).valueDateTime(value);
253 }
254
255 /**
256 * @throws org.sonar.api.utils.text.WriterException on any failure
257 */
258 public JsonWriter prop(String name, @Nullable String value) {
259 return name(name).value(value);
260 }
261
262 /**
263 * @throws org.sonar.api.utils.text.WriterException on any failure
264 */
265 public JsonWriter prop(String name, boolean value) {
266 return name(name).value(value);
267 }
268
269 /**
270 * @throws org.sonar.api.utils.text.WriterException on any failure
271 */
272 public JsonWriter prop(String name, long value) {
273 return name(name).value(value);
274 }
275
276 /**
277 * @throws org.sonar.api.utils.text.WriterException on any failure
278 */
279 public JsonWriter prop(String name, double value) {
280 return name(name).value(value);
281 }
282
283 /**
284 * @throws org.sonar.api.utils.text.WriterException on any failure
285 */
286 public void close() {
287 try {
288 stream.close();
289 } catch (Exception e) {
290 throw rethrow(e);
291 }
292 }
293
294 private IllegalStateException rethrow(Exception e) {
295 // stacktrace is not helpful
296 throw new WriterException("Fail to write JSON: " + e.getMessage());
297 }
298 }