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}