001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011-2014 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.json; 040 041import java.io.IOException; 042import java.util.ArrayDeque; 043import java.util.Deque; 044 045import javax.json.stream.JsonGenerator; 046 047import org.dcm4che3.data.Attributes; 048import org.dcm4che3.data.BulkData; 049import org.dcm4che3.data.Fragments; 050import org.dcm4che3.data.PersonName; 051import org.dcm4che3.data.PersonName.Group; 052import org.dcm4che3.data.Sequence; 053import org.dcm4che3.data.SpecificCharacterSet; 054import org.dcm4che3.data.Tag; 055import org.dcm4che3.data.VR; 056import org.dcm4che3.data.Value; 057import org.dcm4che3.io.DicomInputHandler; 058import org.dcm4che3.io.DicomInputStream; 059import org.dcm4che3.io.DicomInputStream.IncludeBulkData; 060import org.dcm4che3.util.Base64; 061import org.dcm4che3.util.StringUtils; 062import org.dcm4che3.util.TagUtils; 063import org.slf4j.Logger; 064import org.slf4j.LoggerFactory; 065 066/** 067 * @author Gunter Zeilinger <gunterze@gmail.com> 068 * 069 */ 070public class JSONWriter implements DicomInputHandler { 071 072 private static final Logger LOG = LoggerFactory.getLogger(JSONWriter.class); 073 074 private final JsonGenerator gen; 075 private final Deque<Boolean> hasItems = new ArrayDeque<Boolean>(); 076 077 public JSONWriter(JsonGenerator gen) { 078 this.gen = gen; 079 } 080 081 public void write(Attributes attrs) { 082 gen.writeStartObject(); 083 writeAttributes(attrs); 084 gen.writeEnd(); 085 } 086 087 public void writeAttributes(Attributes attrs) { 088 final SpecificCharacterSet cs = attrs.getSpecificCharacterSet(); 089 try { 090 attrs.accept(new Attributes.Visitor() { 091 @Override 092 public boolean visit(Attributes attrs, int tag, VR vr, Object value) 093 throws Exception { 094 writeAttribute(tag, vr, value, cs, attrs); 095 return true; 096 } 097 }, 098 false); 099 } catch (Exception e) { 100 throw new RuntimeException(e); 101 } 102 } 103 104 private void writeAttribute(int tag, VR vr, Object value, 105 SpecificCharacterSet cs, Attributes attrs) { 106 if (TagUtils.isGroupLength(tag)) 107 return; 108 109 gen.writeStartObject(TagUtils.toHexString(tag)); 110 gen.write("vr", vr.name()); 111 if (value instanceof Value) 112 writeValue((Value) value, attrs.bigEndian()); 113 else 114 writeValue(vr, value, attrs.bigEndian(), 115 attrs.getSpecificCharacterSet(vr), true); 116 gen.writeEnd(); 117 } 118 119 private void writeValue(Value value, boolean bigEndian) { 120 if (value.isEmpty()) 121 return; 122 123 if (value instanceof Sequence) { 124 gen.writeStartArray("Value"); 125 for (Attributes item : (Sequence) value) { 126 write(item); 127 } 128 gen.writeEnd(); 129 } else if (value instanceof Fragments) { 130 Fragments frags = (Fragments) value; 131 if (frags.size() > 1 && frags.get(1) instanceof BulkData) { 132 writeBulkData(BulkData.fromFragments(frags)); 133 } else { 134 gen.writeStartArray("DataFragment"); 135 for (Object frag : frags) { 136 gen.writeStartObject(); 137 if (!(frag instanceof Value && ((Value) frag).isEmpty())) 138 writeInlineBinary(frags.vr(), (byte[]) frag, bigEndian, true); 139 gen.writeEnd(); 140 } 141 gen.writeEnd(); 142 } 143 } else if (value instanceof BulkData) { 144 writeBulkData((BulkData) value); 145 } 146 } 147 148 @Override 149 public void readValue(DicomInputStream dis, Attributes attrs) 150 throws IOException { 151 int tag = dis.tag(); 152 VR vr = dis.vr(); 153 int len = dis.length(); 154 if (TagUtils.isGroupLength(tag)) { 155 dis.readValue(dis, attrs); 156 } else if (dis.getIncludeBulkData() == IncludeBulkData.NO 157 && dis.isBulkData(attrs)) { 158 if (len == -1) 159 dis.readValue(dis, attrs); 160 else 161 dis.skipFully(len); 162 } else { 163 gen.writeStartObject(TagUtils.toHexString(tag)); 164 gen.write("vr", vr.name()); 165 if (vr == VR.SQ || len == -1) { 166 hasItems.addLast(false); 167 dis.readValue(dis, attrs); 168 if (hasItems.removeLast()) 169 gen.writeEnd(); 170 if (vr != VR.SQ && dis.getIncludeFragmentBulkData() == IncludeBulkData.URI) { 171 writeBulkData(BulkData.fromFragments((Fragments) attrs.remove(attrs.privateCreatorOf(tag), tag))); 172 } 173 } else if (len > 0) { 174 if (dis.getIncludeBulkData() == IncludeBulkData.URI 175 && dis.isBulkData(attrs)) { 176 writeBulkData(dis.createBulkData()); 177 } else { 178 byte[] b = dis.readValue(); 179 if (tag == Tag.TransferSyntaxUID 180 || tag == Tag.SpecificCharacterSet) 181 attrs.setBytes(tag, vr, b); 182 writeValue(vr, b, dis.bigEndian(), 183 attrs.getSpecificCharacterSet(vr), false); 184 } 185 } 186 gen.writeEnd(); 187 } 188 } 189 190 private void writeValue(VR vr, Object val, boolean bigEndian, 191 SpecificCharacterSet cs, boolean preserve) { 192 switch (vr) { 193 case AE: 194 case AS: 195 case AT: 196 case CS: 197 case DA: 198 case DS: 199 case DT: 200 case IS: 201 case LO: 202 case LT: 203 case PN: 204 case SH: 205 case ST: 206 case TM: 207 case UC: 208 case UI: 209 case UR: 210 case UT: 211 writeStringValues(vr, val, bigEndian, cs); 212 break; 213 case FL: 214 case FD: 215 writeDoubleValues(vr, val, bigEndian); 216 break; 217 case SL: 218 case SS: 219 case UL: 220 case US: 221 writeIntValues(vr, val, bigEndian); 222 break; 223 case OB: 224 case OD: 225 case OF: 226 case OL: 227 case OW: 228 case UN: 229 writeInlineBinary(vr, (byte[]) val, bigEndian, preserve); 230 break; 231 case SQ: 232 assert true; 233 } 234 } 235 236 private void writeStringValues(VR vr, Object val, boolean bigEndian, 237 SpecificCharacterSet cs) { 238 gen.writeStartArray("Value"); 239 Object o = vr.toStrings(val, bigEndian, cs); 240 String[] ss = (o instanceof String[]) 241 ? (String[]) o 242 : new String[]{ (String) o }; 243 for (String s : ss) { 244 if (s == null || s.isEmpty()) 245 gen.writeNull(); 246 else switch (vr) { 247 case DS: 248 try { 249 gen.write(StringUtils.parseDS(s)); 250 } catch (NumberFormatException e) { 251 LOG.info("illegal DS value: {} - encoded as null", s); 252 gen.writeNull(); 253 } 254 break; 255 case IS: 256 try { 257 gen.write(StringUtils.parseIS(s)); 258 } catch (NumberFormatException e) { 259 LOG.info("illegal IS value: {} - encoded as null", s); 260 gen.writeNull(); 261 } 262 break; 263 case PN: 264 writePersonName(s); 265 break; 266 default: 267 gen.write(s); 268 } 269 } 270 gen.writeEnd(); 271 } 272 273 private void writeDoubleValues(VR vr, Object val, boolean bigEndian) { 274 gen.writeStartArray("Value"); 275 int vm = vr.vmOf(val); 276 for (int i = 0; i < vm; i++) { 277 gen.write(vr.toDouble(val, bigEndian, i, 0)); 278 } 279 gen.writeEnd(); 280 } 281 282 private void writeIntValues(VR vr, Object val, boolean bigEndian) { 283 gen.writeStartArray("Value"); 284 int vm = vr.vmOf(val); 285 for (int i = 0; i < vm; i++) { 286 gen.write(vr.toInt(val, bigEndian, i, 0)); 287 } 288 gen.writeEnd(); 289 } 290 291 private void writePersonName(String s) { 292 PersonName pn = new PersonName(s, true); 293 gen.writeStartObject(); 294 writePNGroup("Alphabetic", pn, PersonName.Group.Alphabetic); 295 writePNGroup("Ideographic", pn, PersonName.Group.Ideographic); 296 writePNGroup("Phonetic", pn, PersonName.Group.Phonetic); 297 gen.writeEnd(); 298 } 299 300 private void writePNGroup(String name, PersonName pn, Group group) { 301 if (pn.contains(group)) 302 gen.write(name, pn.toString(group, true)); 303 } 304 305 private void writeInlineBinary(VR vr, byte[] b, boolean bigEndian, 306 boolean preserve) { 307 if (bigEndian) 308 b = vr.toggleEndian(b, preserve); 309 gen.write("InlineBinary", encodeBase64(b)); 310 } 311 312 private String encodeBase64(byte[] b) { 313 int len = (b.length * 4 / 3 + 3) & ~3; 314 char[] ch = new char[len]; 315 Base64.encode(b, 0, b.length, ch, 0); 316 return new String(ch); 317 } 318 319 private void writeBulkData(BulkData blkdata) { 320 gen.write("BulkDataURI", blkdata.uri); 321 } 322 323 @Override 324 public void readValue(DicomInputStream dis, Sequence seq) 325 throws IOException { 326 if (!hasItems.getLast()) { 327 gen.writeStartArray("Value"); 328 hasItems.removeLast(); 329 hasItems.addLast(true); 330 } 331 gen.writeStartObject(); 332 dis.readValue(dis, seq); 333 gen.writeEnd(); 334 } 335 336 @Override 337 public void readValue(DicomInputStream dis, Fragments frags) 338 throws IOException { 339 int len = dis.length(); 340 switch (dis.getIncludeFragmentBulkData()) { 341 case NO: 342 dis.skipFully(len); 343 break; 344 case URI: 345 frags.add(len > 0 ? dis.createBulkData() : null); 346 break; 347 case YES: 348 if (!hasItems.getLast()) { 349 gen.writeStartArray("DataFragment"); 350 hasItems.removeLast(); 351 hasItems.add(true); 352 } 353 354 gen.writeStartObject(); 355 if (len > 0) 356 writeInlineBinary(frags.vr(), dis.readValue(), dis.bigEndian(), false); 357 gen.writeEnd(); 358 } 359 } 360 361 @Override 362 public void startDataset(DicomInputStream dis) throws IOException { 363 gen.writeStartObject(); 364 } 365 366 @Override 367 public void endDataset(DicomInputStream dis) throws IOException { 368 gen.writeEnd(); 369 } 370 371}