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 }