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
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.io;
040
041import java.io.BufferedInputStream;
042import java.io.EOFException;
043import java.io.File;
044import java.io.FileInputStream;
045import java.io.FileOutputStream;
046import java.io.FilterInputStream;
047import java.io.IOException;
048import java.io.InputStream;
049import java.io.ObjectInputStream;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.Collections;
053import java.util.List;
054import java.util.zip.Inflater;
055import java.util.zip.InflaterInputStream;
056
057import org.dcm4che3.data.Attributes;
058import org.dcm4che3.data.BulkData;
059import org.dcm4che3.data.DatasetWithFMI;
060import org.dcm4che3.data.ElementDictionary;
061import org.dcm4che3.data.Fragments;
062import org.dcm4che3.data.ItemPointer;
063import org.dcm4che3.data.Sequence;
064import org.dcm4che3.data.Tag;
065import org.dcm4che3.data.UID;
066import org.dcm4che3.data.VR;
067import org.dcm4che3.util.ByteUtils;
068import org.dcm4che3.util.SafeClose;
069import org.dcm4che3.util.StreamUtils;
070import org.dcm4che3.util.TagUtils;
071import org.slf4j.Logger;
072import org.slf4j.LoggerFactory;
073
074/**
075 * @author Gunter Zeilinger <gunterze@gmail.com>
076 */
077public class DicomInputStream extends FilterInputStream
078    implements DicomInputHandler {
079
080    public enum IncludeBulkData { NO, YES, URI }
081
082    private static final Logger LOG = 
083        LoggerFactory.getLogger(DicomInputStream.class);
084
085    private static final String UNEXPECTED_NON_ZERO_ITEM_LENGTH =
086        "Unexpected item value of {} #{} @ {}";
087    private static final String UNEXPECTED_ATTRIBUTE =
088        "Unexpected attribute {} #{} @ {}";
089    private static final String MISSING_TRANSFER_SYNTAX =
090        "Missing Transfer Syntax (0002,0010) - assume Explicit VR Little Endian";
091    private static final String MISSING_FMI_LENGTH =
092        "Missing or wrong File Meta Information Group Length (0002,0000)";
093    private static final String NOT_A_DICOM_STREAM = 
094        "Not a DICOM Stream";
095    private static final String IMPLICIT_VR_BIG_ENDIAN =
096        "Implicit VR Big Endian encoded DICOM Stream";
097    private static final String DEFLATED_WITH_ZLIB_HEADER =
098        "Deflated DICOM Stream with ZLIB Header";
099
100    private static final int ZLIB_HEADER = 0x789c;
101    private static final int DEF_ALLOCATE_LIMIT = 0x4000000; // 64MiB
102
103    private int allocateLimit = DEF_ALLOCATE_LIMIT;
104    private String uri;
105    private String tsuid;
106    private byte[] preamble;
107    private Attributes fileMetaInformation;
108    private boolean hasfmi;
109    private boolean bigEndian;
110    private boolean explicitVR;
111    private IncludeBulkData includeBulkData = IncludeBulkData.YES;
112    private IncludeBulkData includeFragmentBulkData;
113    private long pos;
114    private long fmiEndPos = -1L;
115    private long tagPos;
116    private long markPos;
117    private int tag;
118    private VR vr;
119    private int length;
120    private DicomInputHandler handler = this;
121    private BulkDataDescriptor bulkDataDescriptor = BulkDataDescriptor.DEFAULT;
122    private final byte[] buffer = new byte[12];
123    private ItemPointer[] itemPointers = {};
124    private boolean decodeUNWithIVRLE = true;
125    private boolean addBulkDataReferences;
126
127    private boolean catBlkFiles = true;
128    private String blkFilePrefix = "blk";
129    private String blkFileSuffix;
130    private File blkDirectory;
131    private ArrayList<File> blkFiles;
132    private String blkURI;
133    private FileOutputStream blkOut;
134    private long blkOutPos;
135
136    public DicomInputStream(InputStream in, String tsuid) throws IOException {
137        super(in);
138        switchTransferSyntax(tsuid);
139    }
140
141    public DicomInputStream(InputStream in) throws IOException {
142        super(in.markSupported() ? in : new BufferedInputStream(in));
143        guessTransferSyntax();
144    }
145
146    public DicomInputStream(File file) throws IOException {
147        this(new FileInputStream(file));
148        uri = file.toURI().toString();
149    }
150
151    public final String getTransferSyntax() {
152        return tsuid;
153    }
154
155    /** 
156     * Returns the limit of initial allocated memory for element values.
157     * 
158     * By default, the limit is set to 67108864 (64 MiB).
159     *
160     * @return Limit of initial allocated memory for value or -1 for no limit
161     * @see #setAllocateLimit(int)
162     */
163    public final int getAllocateLimit() {
164        return allocateLimit;
165    }
166
167    /**
168     * Sets the limit of initial allocated memory for element values. If the
169     * value length exceeds the limit, a byte array with the specified size is
170     * allocated. If the array can filled with bytes read from this
171     * <code>DicomInputStream</code>, the byte array is reallocated with
172     * twice the previous length and filled again. That continues until
173     * the twice of the previous length exceeds the actual value length. Then
174     * the byte array is reallocated with actual value length and filled with
175     * the remaining bytes for the value from this <code>DicomInputStream</code>.
176     * 
177     * The rational of the incrementing allocation of byte arrays is to avoid
178     * OutOfMemoryErrors on parsing corrupted DICOM streams.
179     * 
180     * By default, the limit is set to 67108864 (64 MiB).
181     * 
182     * @param allocateLimit limit of initial allocated memory or -1 for no limit
183     * 
184     */
185    public final void setAllocateLimit(int allocateLimit) {
186        this.allocateLimit = allocateLimit;
187    }
188
189    public final String getURI() {
190        return uri;
191    }
192
193    public final void setURI(String uri) {
194        this.uri = uri;
195    }
196
197    public final IncludeBulkData getIncludeBulkData() {
198        return includeBulkData;
199    }
200
201    public final void setIncludeBulkData(IncludeBulkData includeBulkData) {
202        if (includeBulkData == null)
203            throw new NullPointerException();
204        this.includeBulkData = includeBulkData;
205    }
206
207    public final IncludeBulkData getIncludeFragmentBulkData() {
208        return includeFragmentBulkData;
209    }
210
211    public final BulkDataDescriptor getBulkDataDescriptor() {
212        return bulkDataDescriptor;
213    }
214
215    public final void setBulkDataDescriptor(BulkDataDescriptor bulkDataDescriptor) {
216        this.bulkDataDescriptor = bulkDataDescriptor;
217    }
218
219    public final String getBulkDataFilePrefix() {
220        return blkFilePrefix;
221    }
222
223    public final void setBulkDataFilePrefix(String blkFilePrefix) {
224        this.blkFilePrefix = blkFilePrefix;
225    }
226
227    public final String getBulkDataFileSuffix() {
228        return blkFileSuffix;
229    }
230
231    public final void setBulkDataFileSuffix(String blkFileSuffix) {
232        this.blkFileSuffix = blkFileSuffix;
233    }
234
235    public final File getBulkDataDirectory() {
236        return blkDirectory;
237    }
238
239    public final void setBulkDataDirectory(File blkDirectory) {
240        this.blkDirectory = blkDirectory;
241    }
242
243    public final boolean isConcatenateBulkDataFiles() {
244        return catBlkFiles;
245    }
246
247    public final void setConcatenateBulkDataFiles(boolean catBlkFiles) {
248        this.catBlkFiles = catBlkFiles;
249    }
250
251    public final List<File> getBulkDataFiles() {
252        if (blkFiles != null)
253            return blkFiles;
254        else
255            return Collections.emptyList();
256    }
257
258    public final void setDicomInputHandler(DicomInputHandler handler) {
259        if (handler == null)
260            throw new NullPointerException("handler");
261        this.handler = handler;
262    }
263
264    public boolean isDecodeUNWithIVRLE() {
265        return decodeUNWithIVRLE;
266    }
267
268    public void setDecodeUNWithIVRLE(boolean decodeUNWithIVRLE) {
269        this.decodeUNWithIVRLE = decodeUNWithIVRLE;
270    }
271
272    public boolean isAddBulkDataReferences() {
273        return addBulkDataReferences;
274    }
275
276    public void setAddBulkDataReferences(boolean addBulkDataReferences) {
277        this.addBulkDataReferences = addBulkDataReferences;
278    }
279
280    public final void setFileMetaInformationGroupLength(byte[] val) {
281        fmiEndPos = pos + ByteUtils.bytesToInt(val, 0, bigEndian);
282    }
283
284    public final byte[] getPreamble() {
285        return preamble;
286    }
287
288    public Attributes getFileMetaInformation() throws IOException {
289        readFileMetaInformation();
290        return fileMetaInformation;
291    }
292
293    public final int level() {
294        return itemPointers.length;
295    }
296
297    public final int tag() {
298        return tag;
299    }
300
301    public final VR vr() {
302        return vr;
303    }
304
305    public final int length() {
306        return length;
307    }
308
309    public final long getPosition() {
310        return pos;
311    }
312
313    public void setPosition(long pos) {
314        this.pos = pos;
315    }
316
317    public long getTagPosition() {
318        return tagPos;
319    }
320
321    public final boolean bigEndian() {
322        return bigEndian;
323    }
324
325    public final boolean explicitVR() {
326        return explicitVR;
327    }
328
329    @Override
330    public void close() throws IOException {
331        SafeClose.close(blkOut);
332        super.close();
333    }
334
335    @Override
336    public synchronized void mark(int readlimit) {
337        super.mark(readlimit);
338        markPos = pos;
339    }
340
341    @Override
342    public synchronized void reset() throws IOException {
343        super.reset();
344        pos = markPos;
345    }
346
347    @Override
348    public final int read() throws IOException {
349        int read = super.read();
350        if (read >= 0)
351            pos++;
352        return read;
353    }
354
355    @Override
356    public final int read(byte[] b, int off, int len) throws IOException {
357        int read = super.read(b, off, len);
358        if (read > 0)
359            pos += read;
360        return read;
361    }
362
363    @Override
364    public final int read(byte[] b) throws IOException {
365        return read(b, 0, b.length);
366    }
367
368    @Override
369    public final long skip(long n) throws IOException {
370        long skip = super.skip(n);
371        pos += skip;
372        return skip;
373    }
374
375    public void skipFully(long n) throws IOException {
376        StreamUtils.skipFully(this, n);
377    }
378
379    public void readFully(byte b[]) throws IOException {
380        readFully(b, 0, b.length);
381    }
382
383    public void readFully(byte b[], int off, int len) throws IOException {
384        StreamUtils.readFully(this, b, off, len);
385    }
386
387    public int readHeader() throws IOException {
388        byte[] buf = buffer;
389        tagPos = pos; 
390        readFully(buf, 0, 8);
391        switch(tag = ByteUtils.bytesToTag(buf, 0, bigEndian)) {
392        case Tag.Item:
393        case Tag.ItemDelimitationItem:
394        case Tag.SequenceDelimitationItem:
395           vr = null;
396           break;
397        default:
398            if (explicitVR) {
399                vr = VR.valueOf(ByteUtils.bytesToVR(buf, 4));
400                if (vr.headerLength() == 8) {
401                    length = ByteUtils.bytesToUShort(buf, 6, bigEndian);
402                    return tag;
403                }
404                readFully(buf, 4, 4);
405            } else {
406                vr = VR.UN;
407            }
408        }
409        length = ByteUtils.bytesToInt(buf, 4, bigEndian);
410        return tag;
411    }
412
413    public Attributes readCommand() throws IOException {
414        if (bigEndian || explicitVR)
415            throw new IllegalStateException(
416                    "bigEndian=" + bigEndian + ", explicitVR=" + explicitVR );
417        Attributes attrs = new Attributes(9);
418        readAttributes(attrs, -1, -1);
419        return attrs;
420    }
421
422    /**
423     * @return file meta information and complete dataset
424     */
425    public DatasetWithFMI readDatasetWithFMI() throws IOException {
426        return readDatasetWithFMI(-1, -1);
427    }
428
429    /**
430     * @param len     maximum length to read in bytes, use -1 for no limit
431     * @param stopTag stop reading at the given Tag, use -1 for no stop tag
432     *
433     * @return file meta information and dataset
434     */
435    public DatasetWithFMI readDatasetWithFMI(int len, int stopTag) throws IOException {
436        Attributes dataset = readDataset(len, stopTag);
437        return new DatasetWithFMI(getFileMetaInformation(), dataset);
438    }
439
440    public Attributes readDataset(int len, int stopTag) throws IOException {
441        handler.startDataset(this);
442        readFileMetaInformation();
443        Attributes attrs = new Attributes(bigEndian, 64);
444        readAttributes(attrs, len, stopTag);
445        attrs.trimToSize();
446        handler.endDataset(this);
447        return attrs;
448    }
449
450    public Attributes readFileMetaInformation() throws IOException {
451        if (!hasfmi)
452            return null;  // No File Meta Information
453        if (fileMetaInformation != null)
454            return fileMetaInformation;  // already read
455
456        Attributes attrs = new Attributes(bigEndian, 9);
457        while (pos != fmiEndPos) {
458            mark(12);
459            readHeader();
460            if (TagUtils.groupNumber(tag) != 2) {
461                LOG.warn(MISSING_FMI_LENGTH);
462                reset();
463                break;
464            }
465             if (vr != null) {
466                if (vr == VR.UN)
467                    vr = ElementDictionary.getStandardElementDictionary()
468                            .vrOf(tag);
469                handler.readValue(this, attrs);
470            } else
471                skipAttribute(UNEXPECTED_ATTRIBUTE);
472        }
473        fileMetaInformation = attrs;
474
475        String tsuid = attrs.getString(Tag.TransferSyntaxUID, null);
476        if (tsuid == null) {
477            LOG.warn(MISSING_TRANSFER_SYNTAX);
478            tsuid = UID.ExplicitVRLittleEndian;
479        }
480        switchTransferSyntax(tsuid);
481        return attrs;
482    }
483
484    public void readAttributes(Attributes attrs, int len, int stopTag)
485            throws IOException {
486        ItemPointer[] prevItemPointers = itemPointers;
487        itemPointers = attrs.itemPointers();
488        boolean undeflen = len == -1;
489        boolean hasStopTag = stopTag != -1;
490        long endPos =  pos + (len & 0xffffffffL);
491        while (undeflen || this.pos < endPos) {
492            try {
493                readHeader();
494            } catch (EOFException e) {
495                if (undeflen && pos == tagPos)
496                    break;
497                throw e;
498            }
499            if (hasStopTag && tag == stopTag)
500                break;
501            if (vr != null) {
502                boolean prevBigEndian = bigEndian;
503                boolean prevExplicitVR = explicitVR;
504                try {
505                    if (vr == VR.UN) {
506                        if (decodeUNWithIVRLE) {
507                            bigEndian = false;
508                            explicitVR = false;
509                        }
510                        vr = ElementDictionary.vrOf(tag,
511                                attrs.getPrivateCreator(tag));
512                        if (vr == VR.UN && length == -1)
513                            vr = VR.SQ; // assumes UN with undefined length are SQ,
514                                        // will fail on UN fragments!
515                    }
516                    handler.readValue(this, attrs);
517                } finally {
518                    bigEndian = prevBigEndian;
519                    explicitVR = prevExplicitVR;
520                }
521            } else
522                skipAttribute(UNEXPECTED_ATTRIBUTE);
523        }
524        itemPointers = prevItemPointers;
525    }
526
527    @Override
528    public void readValue(DicomInputStream dis, Attributes attrs)
529            throws IOException {
530        checkIsThis(dis);
531        if (includeBulkData == IncludeBulkData.NO && length != -1 && isBulkData(attrs)) {
532            skipFully(length);
533        } else if (length == 0) {
534            attrs.setNull(tag, vr);
535        } else if (vr == VR.SQ) {
536            readSequence(length, attrs, tag);
537        } else if (length == -1) {
538            readFragments(attrs, tag, vr);
539        } else if (length == BulkData.MAGIC_LEN
540                && super.in instanceof ObjectInputStream) {
541            attrs.setValue(tag, vr, BulkData.deserializeFrom(
542                    (ObjectInputStream) super.in));
543        } else if (includeBulkData == IncludeBulkData.URI && isBulkData(attrs)) {
544            BulkData bulkData = createBulkData();
545            attrs.setValue(tag, vr, bulkData);
546            if (addBulkDataReferences) {
547                attrs.getRoot().addBulkDataReference(
548                        attrs.privateCreatorOf(tag),
549                        tag,
550                        vr,
551                        bulkData,
552                        attrs.itemPointers());
553            }
554        } else {
555            byte[] b = readValue();
556            if (!TagUtils.isGroupLength(tag)) {
557                if (bigEndian != attrs.bigEndian())
558                    vr.toggleEndian(b, false);
559                attrs.setBytes(tag, vr, b);
560            } else if (tag == Tag.FileMetaInformationGroupLength)
561                setFileMetaInformationGroupLength(b);
562        }
563    }
564
565    public BulkData createBulkData() throws IOException {
566            BulkData bulkData;
567        if (uri != null && !(super.in instanceof InflaterInputStream)) {
568            bulkData = new BulkData(uri, pos, length, bigEndian);
569            skipFully(length);
570        } else {
571            if (blkOut == null) {
572                File blkfile = File.createTempFile(blkFilePrefix,
573                        blkFileSuffix, blkDirectory);
574                if (blkFiles == null)
575                    blkFiles = new ArrayList<File>();
576                blkFiles.add(blkfile);
577                blkURI = blkfile.toURI().toString();
578                blkOut = new FileOutputStream(blkfile);
579                blkOutPos = 0L;
580            }
581            try {
582                StreamUtils.copy(this, blkOut, length);
583            } finally {
584                if (!catBlkFiles) {
585                    SafeClose.close(blkOut);
586                    blkOut = null;
587                }
588            }
589            bulkData = new BulkData(blkURI, blkOutPos, length, bigEndian);
590            blkOutPos += length;
591        }
592        return bulkData;
593    }
594
595    public boolean isBulkData(Attributes attrs) {
596        return bulkDataDescriptor.isBulkData(
597                attrs.getPrivateCreator(tag), tag, vr, length, itemPointers);
598    }
599
600    @Override
601    public void readValue(DicomInputStream dis, Sequence seq)
602            throws IOException {
603        checkIsThis(dis);
604        if (length == 0) {
605            seq.add(new Attributes(seq.getParent().bigEndian(), 0));
606            return;
607        }
608        Attributes attrs = new Attributes(seq.getParent().bigEndian());
609        seq.add(attrs);
610        readAttributes(attrs, length, Tag.ItemDelimitationItem);
611        attrs.trimToSize();
612    }
613
614    @Override
615    public void readValue(DicomInputStream dis, Fragments frags)
616            throws IOException {
617        checkIsThis(dis);
618        if (includeFragmentBulkData == IncludeBulkData.NO) {
619            skipFully(length);
620        } else if (length == 0) {
621            frags.add(ByteUtils.EMPTY_BYTES);
622        } else if (length == BulkData.MAGIC_LEN
623                && super.in instanceof ObjectInputStream) {
624            frags.add(BulkData.deserializeFrom((ObjectInputStream) super.in));
625        } else if (includeFragmentBulkData == IncludeBulkData.URI) {
626            frags.add(createBulkData());
627        } else {
628            byte[] b = readValue();
629            if (bigEndian != frags.bigEndian())
630                frags.vr().toggleEndian(b, false);
631            frags.add(b);
632        }
633    }
634
635    @Override
636    public void startDataset(DicomInputStream dis) {
637    }
638
639    @Override
640    public void endDataset(DicomInputStream dis) {
641    }
642
643    private void checkIsThis(DicomInputStream dis) {
644        if (dis != this)
645            throw new IllegalArgumentException("dis != this");
646    }
647
648    private void skipAttribute(String message) throws IOException {
649        LOG.warn(message,
650                 new Object[] { TagUtils.toString(tag), length, tagPos });
651        skip(length);
652    }
653
654    private void readSequence(int len, Attributes attrs, int sqtag)
655            throws IOException {
656        if (len == 0) {
657            attrs.setNull(sqtag, VR.SQ);
658            return;
659        }
660        Sequence seq = attrs.newSequence(sqtag, 10);
661        String privateCreator = attrs.getPrivateCreator(sqtag);
662        boolean undefLen = len == -1;
663        long endPos = pos + (len & 0xffffffffL);
664        for (int i = 0; undefLen || pos < endPos; ++i) {
665            readHeader();
666            if (tag == Tag.Item) {
667                handler.readValue(this, seq);
668            } else if (tag == Tag.SequenceDelimitationItem) {
669                if (length != 0)
670                    skipAttribute(UNEXPECTED_NON_ZERO_ITEM_LENGTH);
671                break;
672            } else
673                skipAttribute(UNEXPECTED_ATTRIBUTE);
674        }
675        if (seq.isEmpty())
676            attrs.setNull(sqtag, VR.SQ);
677        else
678            seq.trimToSize();
679    }
680
681    public Attributes readItem() throws IOException {
682        readHeader();
683        if (tag != Tag.Item)
684            throw new IOException("Unexpected attribute "
685                    + TagUtils.toString(tag) + " #" + length + " @ " + pos);
686        Attributes attrs = new Attributes(bigEndian);
687        attrs.setItemPosition(tagPos);
688        readAttributes(attrs, length, Tag.ItemDelimitationItem);
689        attrs.trimToSize();
690        return attrs;
691    }
692
693    private void readFragments(Attributes attrs, int fragsTag, VR vr)
694            throws IOException {
695        includeFragmentBulkData =
696                includeBulkData == IncludeBulkData.YES || isBulkData(attrs)
697                        ? includeBulkData 
698                        : IncludeBulkData.YES;
699
700        String privateCreator = attrs.getPrivateCreator(fragsTag);
701        Fragments frags = new Fragments(privateCreator, fragsTag, vr, attrs.bigEndian(), 10);
702        for (int i = 0; true; ++i) {
703            readHeader();
704            if (tag == Tag.Item) {
705                handler.readValue(this, frags);
706            } else if (tag == Tag.SequenceDelimitationItem) {
707                if (length != 0)
708                    skipAttribute(UNEXPECTED_NON_ZERO_ITEM_LENGTH);
709                break;
710            } else
711                skipAttribute(UNEXPECTED_ATTRIBUTE);
712        }
713        if (frags.isEmpty())
714            attrs.setNull(fragsTag, vr);
715        else {
716            frags.trimToSize();
717            attrs.setValue(fragsTag, vr, frags);
718        }
719    }
720
721    public byte[] readValue() throws IOException {
722        int valLen = length;
723        try {
724            if (valLen < 0)
725                throw new EOFException(); // assume InputStream length < 2 GiB
726            int allocLen = allocateLimit >= 0
727                    ? Math.min(valLen, allocateLimit)
728                    : valLen;
729            byte[] value = new byte[allocLen];
730            readFully(value, 0, allocLen);
731            while (allocLen < valLen) {
732                int newLength = Math.min(valLen, allocLen << 1);
733                value = Arrays.copyOf(value, newLength);
734                readFully(value, allocLen, newLength - allocLen);
735                allocLen = newLength;
736            }
737            return value;
738        } catch (IOException e) {
739            LOG.warn("IOException during read of {} #{} @ {}",
740                    TagUtils.toString(tag), length, tagPos, e);
741            throw e;
742        }
743    }
744
745    private void switchTransferSyntax(String tsuid) throws IOException {
746        this.tsuid = tsuid;
747        bigEndian = tsuid.equals(UID.ExplicitVRBigEndianRetired);
748        explicitVR = !tsuid.equals(UID.ImplicitVRLittleEndian);
749        if (tsuid.equals(UID.DeflatedExplicitVRLittleEndian)
750                        || tsuid.equals(UID.JPIPReferencedDeflate)) {
751            if (hasZLIBHeader()) {
752                LOG.warn(DEFLATED_WITH_ZLIB_HEADER);
753                super.in = new InflaterInputStream(super.in);
754            } else
755                super.in = new InflaterInputStream(super.in,
756                        new Inflater(true));
757        }
758    }
759
760    private boolean hasZLIBHeader() throws IOException {
761        if (!markSupported())
762            return false;
763        byte[] buf = buffer;
764        mark(2);
765        read(buf, 0, 2);
766        reset();
767        return ByteUtils.bytesToUShortBE(buf, 0) == ZLIB_HEADER;
768    }
769
770    private void guessTransferSyntax() throws IOException {
771        byte[] b128 = new byte[128];
772        byte[] buf = buffer;
773        mark(132);
774        int rlen = read(b128);
775        if (rlen == 128) {
776            read(buf, 0, 4);
777            if (buf[0] == 'D' && buf[1] == 'I'
778                    && buf[2] == 'C' && buf[3] == 'M') {
779                preamble = b128.clone();
780                if (!markSupported()) {
781                    hasfmi = true;
782                    tsuid = UID.ExplicitVRLittleEndian;
783                    bigEndian = false;
784                    explicitVR = true;
785                    return;
786                }
787                mark(128);
788                read(b128);
789            }
790        }
791        if (rlen < 8
792                || !guessTransferSyntax(b128, rlen, false)
793                && !guessTransferSyntax(b128, rlen, true))
794            throw new DicomStreamException(NOT_A_DICOM_STREAM);
795        reset();
796        hasfmi = TagUtils.isFileMetaInformation(
797                ByteUtils.bytesToTag(b128, 0, bigEndian));
798    }
799
800    private boolean guessTransferSyntax(byte[] b128, int rlen, boolean bigEndian)
801            throws DicomStreamException {
802        int tag1 = ByteUtils.bytesToTag(b128, 0, bigEndian);
803        VR vr = ElementDictionary.vrOf(tag1, null);
804        if (vr == VR.UN)
805            return false;
806        if (ByteUtils.bytesToVR(b128, 4) == vr.code()) {
807            this.tsuid = bigEndian ? UID.ExplicitVRBigEndianRetired 
808                                   : UID.ExplicitVRLittleEndian;
809            this.bigEndian = bigEndian;
810            this.explicitVR = true;
811            return true;
812        }
813        int len = ByteUtils.bytesToInt(b128, 4, bigEndian);
814        if (len < 0 || 8 + len > rlen)
815            return false;
816
817        if (bigEndian)
818            throw new DicomStreamException(IMPLICIT_VR_BIG_ENDIAN);
819
820        this.tsuid = UID.ImplicitVRLittleEndian;
821        this.bigEndian = false;
822        this.explicitVR = false;
823        return true;
824    }
825}