/**
  * Get the values of this attribute as multiple short arrays, one per frame.
  *
  * <p>Caller needs to supply the number for frames so that pixel data can be split across
  * per-frame arrays (since not necessarily known when this attribute was created or read.
  *
  * <p>This allocates new arrays of sufficient length, which may fail if they are too large, and
  * defeats the point of leaving the values on disk in the first place. However, it is a fallback
  * for when the caller does not want to go to the trouble of creating a {@link
  * java.nio.MappedByteBuffer MappedByteBuffer} from which to get a {@link java.nio.ShortBuffer
  * ShortBuffer} from the file, or more likely is not even aware that the attribute values have
  * been left on disk.
  *
  * @param numberOfFrames the number of frames
  * @return the values as an array of arrays of bytes
  * @throws DicomException thrown if values cannot be read
  */
 public short[][] getShortValuesPerFrame(int numberOfFrames) throws DicomException {
   // System.err.println("OtherWordAttributeOnDisk.getShortValuesPerFrame(): lazy read of of all
   // frames into heap allocated memory as per-frame arrays, rather than using memory mapped buffer
   // :(");
   short[][] v = null;
   if (valueLength > 0) {
     long bytesperframe = valueLength / numberOfFrames;
     int len = (int) (bytesperframe / 2);
     v = new short[numberOfFrames][];
     try {
       BinaryInputStream i =
           new BinaryInputStream(
               new FileInputStream(file), false /*bigEndian - byte order is irrelevant*/);
       i.skipInsistently(byteOffset);
       for (int f = 0; f < numberOfFrames; ++f) {
         short[] buffer = new short[len];
         v[f] = buffer;
         i.readUnsigned16(buffer, 0, len);
       }
       i.close();
     } catch (IOException e) {
       e.printStackTrace(System.err);
       throw new DicomException(
           "Failed to read value (length "
               + valueLength
               + " dec) in delayed read of "
               + ValueRepresentation.getAsString(getVR())
               + " attribute "
               + getTag());
     }
   }
   return v;
 }
 /**
  * Get the value of this attribute as a short array for one selected frame.
  *
  * <p>This allocates a new array of sufficient length, which may fail if it is too large, and
  * defeats the point of leaving the values on disk in the first place. However, it is a fallback
  * for when the caller does not want to go to the trouble of creating a {@link
  * java.nio.MappedByteBuffer MappedByteBuffer} from which to get a {@link java.nio.ShortBuffer
  * ShortBuffer} from the file, or more likely is not even aware that the attribute values have
  * been left on disk, because {@link com.pixelmed.dicom.AttributeFactory AttributeFactory}
  * silently created an instance of this class rather than an in-memory {@link
  * com.pixelmed.dicom.OtherWordAttribute OtherWordAttribute}.
  *
  * @param frameNumber from 0
  * @param numberOfFrames the number of frames
  * @return the values as an array of short
  * @throws DicomException thrown if values cannot be read
  */
 public short[] getShortValuesForSelectedFrame(int frameNumber, int numberOfFrames)
     throws DicomException {
   // System.err.println("OtherWordAttributeOnDisk.getShortValuesForSelectedFrame(): lazy read of
   // selected frame "+frameNumber+" into heap allocated memory, rather than using memory mapped
   // buffer :(");
   short[] buffer = null;
   long bytesperframe = valueLength / numberOfFrames;
   long byteoffsetfromstartofattributevalue = bytesperframe * frameNumber;
   if (byteoffsetfromstartofattributevalue + bytesperframe <= valueLength) {
     int len = (int) (bytesperframe / 2);
     buffer = new short[len];
     try {
       BinaryInputStream i = new BinaryInputStream(new FileInputStream(file), bigEndian);
       i.skipInsistently(byteOffset + byteoffsetfromstartofattributevalue);
       i.readUnsigned16(buffer, 0, len);
       i.close();
     } catch (IOException e) {
       throw new DicomException(
           "Failed to read frame "
               + frameNumber
               + " of "
               + numberOfFrames
               + " frames, size "
               + bytesperframe
               + " dec and offset "
               + byteoffsetfromstartofattributevalue
               + " dec bytes in delayed read of "
               + ValueRepresentation.getAsString(getVR())
               + " attribute "
               + getTag());
     }
   } else {
     throw new DicomException(
         "Requested frame "
             + frameNumber
             + " of "
             + numberOfFrames
             + " frames, size "
             + bytesperframe
             + " dec and offset "
             + byteoffsetfromstartofattributevalue
             + " dec bytes to read value exceeds length "
             + valueLength
             + " dec in delayed read of "
             + ValueRepresentation.getAsString(getVR())
             + " attribute "
             + getTag());
   }
   return buffer;
 }
 /**
  * Construct a byte ordered stream from the supplied stream.
  *
  * <p>The byte order may be changed later.
  *
  * @param i the input stream to read from
  */
 public EncapsulatedInputStream(BinaryInputStream i) {
   this.i = i;
   bigEndian = i.isBigEndian();
   buffer = new byte[8];
   fragment = null;
   firstTime = true;
   sequenceDelimiterEncountered = false;
   endOfFrameEncountered = false;
 }
 /**
  * Get the values of this attribute as a short array.
  *
  * <p>This allocates a new array of sufficient length, which may fail if it is too large, and
  * defeats the point of leaving the values on disk in the first place. However, it is a fallback
  * for when the caller does not want to go to the trouble of creating a {@link
  * java.nio.MappedByteBuffer MappedByteBuffer} from which to get a {@link java.nio.ShortBuffer
  * ShortBuffer} from the file, or more likely is not even aware that the attribute values have
  * been left on disk, because {@link com.pixelmed.dicom.AttributeFactory AttributeFactory}
  * silently created an instance of this class rather than an in-memory {@link
  * com.pixelmed.dicom.OtherWordAttribute OtherWordAttribute}.
  *
  * @return the values as an array of short
  * @throws DicomException thrown if values cannot be read
  */
 public short[] getShortValues() throws DicomException {
   // System.err.println("OtherWordAttributeOnDisk.getShortValues(): lazy read into heap allocated
   // memory, rather than using memory mapped buffer :(");
   short[] buffer = null;
   if (valueLength > 0) {
     int len = (int) (valueLength / 2);
     buffer = new short[len];
     try {
       BinaryInputStream i = new BinaryInputStream(new FileInputStream(file), bigEndian);
       i.skipInsistently(byteOffset);
       i.readUnsigned16(buffer, 0, len);
       i.close();
     } catch (IOException e) {
       throw new DicomException(
           "Failed to read value (length "
               + valueLength
               + " dec) in delayed read of "
               + ValueRepresentation.getAsString(getVR())
               + " attribute "
               + getTag());
     }
   }
   return buffer;
 }
 private long readItemTag() throws IOException {
   AttributeTag tag = readAttributeTag();
   // System.err.println("EncapsulatedInputStream.readItemTag: tag="+tag);
   long vl = i.readUnsigned32(); // always implicit VR form for items and delimiters
   bytesRead += 4;
   if (tag.equals(TagFromName.SequenceDelimitationItem)) {
     // System.err.println("EncapsulatedInputStream.readItemTag: SequenceDelimitationItem");
     vl = 0; // regardless of what was read
     sequenceDelimiterEncountered = true;
   } else if (!tag.equals(TagFromName.Item)) {
     throw new IOException(
         "Unexpected DICOM tag "
             + tag
             + " (vl="
             + vl
             + ") in encapsulated data whilst expecting Item or SequenceDelimitationItem");
   }
   // System.err.println("EncapsulatedInputStream.readItemTag: length="+vl);
   return vl;
 }
 /**
  * @param i
  * @exception IOException
  */
 private AttributeTag readAttributeTag() throws IOException {
   int group = i.readUnsigned16();
   int element = i.readUnsigned16();
   bytesRead += 4;
   return new AttributeTag(group, element);
 }
 /**
  * Extracts <code>len</code> bytes of data from the current or subsequent fragments.
  *
  * @param b the buffer into which the data is read.
  * @param off the start offset of the data.
  * @param len the number of bytes read.
  * @return the total number of bytes read into the buffer (always whatever was asked for), or -1
  *     if there is no more data because the end of a frame has been reached.
  * @exception IOException if an I/O error occurs.
  */
 public final int read(byte b[], int off, int len) throws IOException {
   // System.err.println("EncapsulatedInputStream.read(byte [],"+off+","+len+")");
   // System.err.println("EncapsulatedInputStream.read() at start,
   // fragmentRemaining="+fragmentRemaining);
   // System.err.println("EncapsulatedInputStream.read() at start,
   // endOfFrameEncountered="+endOfFrameEncountered);
   // System.err.println("EncapsulatedInputStream.read() at start,
   // currentFragmentContainsEndOfFrame="+currentFragmentContainsEndOfFrame);
   if (endOfFrameEncountered) {
     // System.err.println("EncapsulatedInputStream.read() returning -1 since
     // endOfFrameEncountered");
     return -1; // i.e., won't advance until nextFrame() is called to reset this state
   }
   int count = 0;
   int remainingToDo = len;
   while (remainingToDo > 0 && !sequenceDelimiterEncountered && !endOfFrameEncountered) {
     // System.err.println("EncapsulatedInputStream.read() remainingToDo="+remainingToDo);
     if (fragment == null) {
       if (firstTime) {
         // System.err.println("EncapsulatedInputStream.read() firstTime");
         // first time ... skip offset table ...
         long offsetTableLength = readItemTag();
         if (sequenceDelimiterEncountered) {
           throw new IOException("Expected offset table item tag; got sequence delimiter");
         }
         // System.err.println("EncapsulatedInputStream.read() skipping
         // offsetTableLength="+offsetTableLength);
         i.skipInsistently(offsetTableLength);
         bytesRead += offsetTableLength;
         firstTime = false;
       }
       // load a new fragment ...
       // System.err.println("EncapsulatedInputStream.read() loading a new fragment");
       long vl =
           readItemTag(); // if sequenceDelimiterEncountered, vl will be zero and no more will be
                          // done
       if (vl != 0) {
         currentFragmentContainsEndOfFrame = false;
         fragmentRemaining = fragmentSize = (int) vl;
         fragment = new byte[fragmentSize];
         i.readInsistently(fragment, 0, fragmentSize);
         bytesRead += fragmentSize;
         fragmentOffset = 0;
         // System.err.println("EncapsulatedInputStream.read() fragmentRemaining
         // initially="+fragmentRemaining);
         // System.err.println("EncapsulatedInputStream.read() fragment =
         // "+com.pixelmed.utils.HexDump.dump(fragment));
         // Ignore everything between (the last) EOI marker and the end of the fragment
         int positionOfEOI = fragmentRemaining - 1;
         while (--positionOfEOI > 0) {
           int firstMarkerByte = fragment[positionOfEOI] & 0xff;
           int secondMarkerByte = fragment[positionOfEOI + 1] & 0xff;
           // System.err.println("EncapsulatedInputStream.read() fragment
           // fragment["+positionOfEOI+"] = 0x"+Integer.toHexString(firstMarkerByte));
           // System.err.println("EncapsulatedInputStream.read() fragment
           // fragment["+(positionOfEOI+1)+"] = 0x"+Integer.toHexString(secondMarkerByte));
           if (firstMarkerByte == 0xff && secondMarkerByte == 0xd9) {
             currentFragmentContainsEndOfFrame = true;
             break;
           }
         }
         // System.err.println("EncapsulatedInputStream.read() positionOfEOI="+positionOfEOI);
         if (positionOfEOI > 0) { // will be zero if we did not find one
           fragmentRemaining =
               positionOfEOI + 2; // effectively skips all (hopefully padding) bytes after the EOI
         }
         // System.err.println("EncapsulatedInputStream.read() fragmentRemaining after removing
         // trailing padding="+fragmentRemaining);
       }
     }
     int amountToCopyFromThisFragment =
         remainingToDo < fragmentRemaining ? remainingToDo : fragmentRemaining;
     // System.err.println("EncapsulatedInputStream.read()
     // amountToCopyFromThisFragment="+amountToCopyFromThisFragment);
     if (amountToCopyFromThisFragment > 0) {
       System.arraycopy(fragment, fragmentOffset, b, off, amountToCopyFromThisFragment);
       off += amountToCopyFromThisFragment;
       fragmentOffset += amountToCopyFromThisFragment;
       fragmentRemaining -= amountToCopyFromThisFragment;
       remainingToDo -= amountToCopyFromThisFragment;
       count += amountToCopyFromThisFragment;
     }
     if (fragmentRemaining <= 0) {
       fragment = null;
       if (currentFragmentContainsEndOfFrame) {
         endOfFrameEncountered =
             true; // once EOI has been seen in a fragment, use the rest of the fragment including
                   // the EOI, but no further
       }
     }
   }
   // System.err.println("EncapsulatedInputStream.read() returning count="+count);
   // System.err.println("EncapsulatedInputStream.read()
   // returning="+com.pixelmed.utils.HexDump.dump(fragment,off,count));
   return count == 0
       ? -1
       : count; // always returns more than 0 unless end, which is signalled by -1
 }