All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.bio.seq.format.abi.ABIF Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.bio.seq.format.abi;


import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.hfg.bio.Nucleotide;
import com.hfg.bio.seq.NucleicAcid;
import com.hfg.bio.seq.SeqQualityScores;
import com.hfg.bio.seq.format.SeqIOException;
import com.hfg.graphics.ColorUtil;
import com.hfg.graphics.TextUtil;
import com.hfg.html.attribute.HTMLColor;
import com.hfg.svg.SVG;
import com.hfg.svg.SvgGroup;
import com.hfg.svg.SvgPath;
import com.hfg.svg.SvgText;
import com.hfg.svg.path.SvgPathLineToCmd;
import com.hfg.svg.path.SvgPathMoveToCmd;
import com.hfg.util.ByteUtil;
import com.hfg.util.StringUtil;
import com.hfg.util.io.ByteSource;

//------------------------------------------------------------------------------
/**
 * Extracts data from ABIF (ab1) format sequencing chromatographic trace files.
 *
 * @author J. Alex Taylor, hairyfatguy.com
 */
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
// See http://www6.appliedbiosystems.com/support/software_community/ABIF_File_Format.pdf

public class ABIF
{
   private int mVersionNum;
   private String mInstrumentClass;
   private String mInstrumentFamily;
   private String mInstrumentName;
   private String mMachineName;
   private String mInstrumentParameters;
   private String mBaseOrder;
   private Integer mLane;
   private String  mBasecallerSeq;
   private short[] mBasecallerQualityScores;
   private short[] mBasecallerPeakLocations;
   private String  mUserSeq;
   private short[] mUserQualityScores;
   private short[] mUserPeakLocations;
   private Integer mNumTraceDataPoints;
   private Integer mMaxTraceDataValue;
   private Map mTraceDataMap = new HashMap<>(4);
   private String  mSampleName;
   private String  mSampleComment;
   private String  mSampleTrackingID;
   private String  mQC_Warnings;
   private String  mQC_Errors;
   private Long    mQV20_Score;
   private String  mQV20_Status;
   private Date    mRunDate;
   private Integer mMaxQualityValue;

   private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
   private static FontRenderContext sFRC = new FontRenderContext(new AffineTransform(), true, true);

   // These represent the file tags that we are currently parsing
   private enum FileTag
   {
      CMNT,    // Sample comment
      DATA,    // instances 9-12: short[] holding analyzed color data
      FWO_,    // Sequencing Analysis Filter wheel order. Fixed for 3500 at "GATC"
      HCFG,    // 1st instance: The instrument class. All upper case, no spaces.
               // 2nd instance: The instrument family. All upper case, no spaces.
               // 3rd instance: The official instrument name. Mixed case, minus any special formatting.
               // 4th instance: Instrument parameters. Contains key-value pairs of instrument configuration
               //               information, separated by semicolons. Four parameters are included initially:
               //               UnitID=, CPUBoard=, ArraySize=<# of capillaries>,
               //               SerialNumber=.
      LANE,    // Sample's lane or capillary number
      LIMS,    // Sample tracking ID
      MCHN,    // Machine Name.
      PBAS,    // 1st instance: Array of sequence characters edited by user
               // 2nd instance: Array of sequence characters as called by Basecaller
      PCON,    // 1st instance: Array of quality values (0-255) as edited by user
               // 2nd instance: Array of quality values (0-255) as called by Basecaller
      phQL,    // Maximum quality value
      PLOC,    // 1st instance: Array of peak locations edited by user
               // 2nd instance: Array of peak locations as called by Basecaller
      QcRs,    // 1st instance: QC warnings, a concatenated comma-separated string      (3500/3500xl specific?)
               // 2nd instance: QC errors, a concatenated comma-separated string        (3500/3500xl specific?)
      QV20,    // 1st instance: QV20+ value                                             (3500/3500xl specific?)
               // 2nd instance: One of 'Pass', 'Fail', or 'Check'                       (3500/3500xl specific?)
      RUND,    // 1st instance: Run started date
               // 2nd instance: Run stopped date
      RUNT,    // 1st instance: Run started time
               // 2nd instance: Run stopped time
      SMPL     // Sequencing analysis sample name
   }

   //###########################################################################
   // CONSTRUCTORS
   //###########################################################################

   //---------------------------------------------------------------------------
   public ABIF(File inFile)
      throws IOException
   {
      if (! inFile.exists())
      {
         throw new IOException("The specified ABIF file " + StringUtil.singleQuote(inFile.getPath()) + " doesn't exist!");
      }

      if (! inFile.canRead())
      {
         throw new IOException("You do not have read access to the specified ABIF file " + StringUtil.singleQuote(inFile.getPath()) + "!");
      }

      RandomAccessFile randomAccessFile = null;
      try
      {
         randomAccessFile = new RandomAccessFile(inFile,  "r");
         init(new ByteSource(randomAccessFile));
      }
      finally
      {
         randomAccessFile.close();
      }
   }

   //---------------------------------------------------------------------------
   public ABIF(byte[] inBytes)
      throws IOException
   {
      init(new ByteSource(inBytes));
   }

   //###########################################################################
   // PUBLIC METHODS
   //###########################################################################

   //---------------------------------------------------------------------------
   public int getABIF_FormatVersionNum()
   {
      return mVersionNum;
   }

   //---------------------------------------------------------------------------
   public Integer getLane()
   {
      return mLane;
   }

   //---------------------------------------------------------------------------
   public String getInstrumentClass()
   {
      return mInstrumentClass;
   }

   //---------------------------------------------------------------------------
   public String getInstrumentFamily()
   {
      return mInstrumentFamily;
   }

   //---------------------------------------------------------------------------
   public String getInstrumentName()
   {
      return mInstrumentName;
   }

   //---------------------------------------------------------------------------
   public String getMachineName()
   {
      return mMachineName;
   }

   //---------------------------------------------------------------------------
   public Date getRunDate()
   {
      return mRunDate;
   }

   //---------------------------------------------------------------------------
   public String getSampleName()
   {
      return mSampleName;
   }

   //---------------------------------------------------------------------------
   public String getSampleTrackingID()
   {
      return mSampleTrackingID;
   }

   //---------------------------------------------------------------------------
   public String getSampleComment()
   {
      return mSampleComment;
   }

   //---------------------------------------------------------------------------
   public String getBasecallerSeq()
   {
      return mBasecallerSeq;
   }

   //---------------------------------------------------------------------------
   public short[] getBasecallerPeakLocations()
   {
      return mBasecallerPeakLocations;
   }

   //---------------------------------------------------------------------------
   public short[] getBasecallerQualityScores()
   {
      return mBasecallerQualityScores;
   }

   //---------------------------------------------------------------------------
   public String getUserSeq()
   {
      return mUserSeq;
   }

   //---------------------------------------------------------------------------
   public short[] getUserQualityScores()
   {
      return mUserQualityScores;
   }

   //---------------------------------------------------------------------------
   public Integer getMaxQualityScore()
   {
      return mMaxQualityValue;
   }

   //---------------------------------------------------------------------------
   public short[] getUserPeakLocations()
   {
      return mUserPeakLocations;
   }

   //---------------------------------------------------------------------------
   public short[] getTraceValues(Nucleotide inNucleotide)
   {
      return mTraceDataMap.get(Character.toUpperCase(inNucleotide.getOneLetterCode()));
   }

   //---------------------------------------------------------------------------
   /**
    3500/3500xl specific: Returns any QC warnings as a concatenated comma-separated string.
    @return QC warnings
    */
   public String getQC_Warnings()
   {
      return mQC_Warnings;
   }

   //---------------------------------------------------------------------------
   /**
    3500/3500xl specific: Returns any QC errors as a concatenated comma-separated string.
    @return QC errors or null
    */
   public String getQC_Errors()
   {
      return mQC_Errors;
   }

   //---------------------------------------------------------------------------
   /**
    3500/3500xl specific: Returns the QV20+ value.
    @return QV20+ score or null
    */
   public Long getQV20_Score()
   {
      return mQV20_Score;
   }

   //---------------------------------------------------------------------------
   /**
    3500/3500xl specific: Returns the QV20 status as one of 'Pass', 'Fail', or 'Check'.
    @return QV20 status or null
    */
   public String getQV20_Status()
   {
      return mQV20_Status;
   }

   //---------------------------------------------------------------------------
   public NucleicAcid toNucleicAcid()
   {
      NucleicAcid seq = new NucleicAcid().setID(getSampleName());
      if (StringUtil.isSet(getUserSeq()))
      {
         seq.setSequence(StringUtil.isSet(getUserSeq()) ? getUserSeq() : getBasecallerSeq());
      }

      short[] qualityScores = null;
      if (getUserQualityScores() != null)
      {
         qualityScores = getUserQualityScores();
      }
      else if (getBasecallerQualityScores() != null)
      {
         qualityScores = getBasecallerQualityScores();
      }

      if (qualityScores != null)
      {
         seq.setSeqQualityScores(new SeqQualityScores(qualityScores));
      }

      return seq;
   }

   //--------------------------------------------------------------------------
   public int getMaxTraceValue()
   {
      if (null == mMaxTraceDataValue)
      {
         int maxValue = 0;

         for (Character nucleotide : mTraceDataMap.keySet())
         {
            for (short value : mTraceDataMap.get(nucleotide))
            {
               if (value > maxValue)
               {
                  maxValue = value;
               }
            }
         }

         mMaxTraceDataValue = maxValue;
      }

      return mMaxTraceDataValue;
   }

   //--------------------------------------------------------------------------
   public SVG toSVG()
   {
      int height = 400;
      int width = mNumTraceDataPoints * 2;
      Font font = Font.decode("Arial-PLAIN-9");
      int marginSize = 20;

      // Determine the scale.
      double xScalingFactor = width / (float) mNumTraceDataPoints;
      double yScalingFactor = height / (float) getMaxTraceValue();
      double yQualityScalingFactor = height / (float) getMaxQualityScore();

      int scaledMaxY = (int) (getMaxTraceValue() * yScalingFactor);
      int scaledMaxQualityY = (int) (getMaxQualityScore() * yQualityScalingFactor);

      int lineHeight = (int) TextUtil.getStringRect("A", font).getHeight();


      short[] peakLocations = getUserPeakLocations();
      if (null == peakLocations)
      {
         peakLocations = getBasecallerPeakLocations();
      }

      SVG svg = new SVG();
      svg.setFont(font);


      // Display the quality scores in the background
      short[] qualityScores = getUserQualityScores();
      if (null == qualityScores)
      {
         qualityScores = getBasecallerQualityScores();
      }

      if (qualityScores != null)
      {
         SvgGroup group = svg.addGroup().setClass("quality");
         group.addStyle("stroke:#" + ColorUtil.colorToHex(HTMLColor.DARK_GRAY));

         SvgPath path = group.addPath().addPathCommand(new SvgPathMoveToCmd().addPoint(new Point2D.Float(marginSize, height + marginSize)));
         List lineToValues = new ArrayList<>(peakLocations.length * 4);
         float prevPeakX = 0;
         for (int i = 0; i < peakLocations.length; i++)
         {
            float scaledPeakX = marginSize + (float) (peakLocations[i] * xScalingFactor);
            float nextScaledPeakX = (i < qualityScores.length - 1 ? marginSize + (float) (peakLocations[i + 1] * xScalingFactor) : width - marginSize);

            float leftX = (i > 0 ? scaledPeakX - (scaledPeakX - prevPeakX)/2f : marginSize);
            float rightX = (i < qualityScores.length ? scaledPeakX + (nextScaledPeakX - scaledPeakX)/2f : width - marginSize);
            float y = marginSize + scaledMaxQualityY - (int) (qualityScores[i] * yQualityScalingFactor);

            lineToValues.add(leftX);
            lineToValues.add(y);

            lineToValues.add(rightX);
            lineToValues.add(y);

            prevPeakX = scaledPeakX;
         }
         lineToValues.add(width - (float) marginSize);
         lineToValues.add(marginSize + (float) scaledMaxQualityY);

         path.addPathCommand(new SvgPathLineToCmd().setRawNumbers(lineToValues));
         path.setFill(HTMLColor.LIGHT_GRAY).addStyle("fill-opacity:0.4; stroke-opacity:0.2");
      }

      // Display the nucleotide traces
      for (Character nucleotide : new Character[] {'A', 'C', 'G', 'T'})
      {
         short[] traceValues = mTraceDataMap.get(nucleotide);

         SvgGroup group = svg.addGroup().setClass(nucleotide + "");
         group.addStyle("stroke:#" + ColorUtil.colorToHex(getColorForNucleotide(nucleotide)));

         SvgPath path = group.addPath().addPathCommand(new SvgPathMoveToCmd().addPoint(new Point2D.Float(marginSize, height - marginSize)));
         List lineToValues = new ArrayList<>(traceValues.length * 2);
         for (int i = 0; i < traceValues.length; i++)
         {
            float x = marginSize + (int) (i * xScalingFactor);
            float y = marginSize + scaledMaxY - (int) (traceValues[i] * yScalingFactor);

            lineToValues.add(x);
            lineToValues.add(y);
         }

         path.addPathCommand(new SvgPathLineToCmd().setRawNumbers(lineToValues));
         path.setFill(null);
      }

      // Display the called (or user-specified) sequence along the top
      String sequence = getUserSeq();
      if (null == sequence)
      {
         sequence = getBasecallerSeq();
      }

      if (StringUtil.isSet(sequence)
            && peakLocations != null)
      {
         SvgGroup group = svg.addGroup().setClass("sequence");

         // Used to better calculate text placement
         Rectangle2D textBoundBox = font.getStringBounds("A", 0, "A".length(), sFRC);

         for (int i = 0; i < peakLocations.length; i++)
         {
            short peakLocation = peakLocations[i];
            char nucleotide = sequence.charAt(i);

            float x = marginSize + (int) ((peakLocation * xScalingFactor) - (textBoundBox.getWidth() / 2));
            float y = marginSize;
            SvgText label = group.addText(nucleotide + "", font, new Point.Float(x, y));

            label.setFill(getColorForNucleotide(nucleotide));

            if ((i+1)%10 == 0)
            {
               x = marginSize + (int) (peakLocation * xScalingFactor);
               y = marginSize + lineHeight;
                           
               group.addText((i + 1) + "", font, new Point.Float(x, y)).setFill(Color.LIGHT_GRAY);
            }
         }
      }


      return svg;
   }

   //--------------------------------------------------------------------------
   Color getColorForNucleotide(Character inNucleotide)
   {
      Color color;
      switch (inNucleotide)
      {
         case 'A':
            color = HTMLColor.GREEN;
            break;
         case 'C':
            color = HTMLColor.BLUE;
            break;
         case 'G':
            color = HTMLColor.BLACK;
            break;
         case 'T':
            color = HTMLColor.RED;
            break;
         default:
            color = HTMLColor.DARK_GRAY;
      }

      return color;
   }

   //###########################################################################
   // PRIVATE METHODS
   //###########################################################################

   //---------------------------------------------------------------------------
   private void init(ByteSource inByteSource)
         throws IOException
   {
      DirectoryEntry header = readHeader(inByteSource);

      // Skip to the directory entries
      inByteSource.seek(header.getDataOffset());

      // Read directory entries (located at the end of the file)
      List dirEntries = new ArrayList<>(header.getNumElements());
      for (int i = 0; i < header.getNumElements(); i++)
      {
         dirEntries.add(new DirectoryEntry(inByteSource));
      }

      extractData(inByteSource, dirEntries);
   }

   //---------------------------------------------------------------------------
   private DirectoryEntry readHeader(ByteSource inByteSource)
      throws IOException
   {
      byte[] fileSignature = new byte[4];
      inByteSource.read(fileSignature);

      if (! new String(fileSignature).equals("ABIF"))
      {
         throw new SeqIOException("ABIF file signature not found!");
      }

      mVersionNum = ByteUtil.get2ByteInt(inByteSource, mByteOrder);

      // Read the dir entry
      DirectoryEntry dirEntry = new DirectoryEntry(inByteSource);

      // The dataSize should be exactly the size required for the entries (numElements x elementSize)
      // TODO: Add sanity check

      return dirEntry;
   }

   //---------------------------------------------------------------------------
   private void extractData(ByteSource inByteSource, List inDirEntries)
      throws IOException
   {
      // Create a temporary list for the trace value data
      List traceValueList = new ArrayList<>(4);
      for (int i = 0; i < 4; i++)
      {
         traceValueList.add(null);
      }

      for (DirectoryEntry dirEntry : inDirEntries)
      {
         if (dirEntry.getTagName().equals(FileTag.CMNT.name()))
         {
            mSampleComment = getStringValueForDirectoryEntry(inByteSource, dirEntry);
         }
         else if (dirEntry.getTagName().equals(FileTag.DATA.name()))
         {
            if (dirEntry.getTagNum() >= 9
                && dirEntry.getTagNum() <= 12)
            {
               inByteSource.seek(dirEntry.getDataOffset());
               short[] traceValues = ByteUtil.getShortArray(inByteSource, dirEntry.getNumElements(), mByteOrder);

               int index = dirEntry.getTagNum() - 9;

               // Since we may not yet know the base order, store the trace values in an array temporarily;
               traceValueList.set(index, traceValues);

               if (null == mNumTraceDataPoints)
               {
                  mNumTraceDataPoints = traceValues.length;
               }
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.FWO_.name()))
         {
            mBaseOrder = new String(ByteUtil.getBytesFromInt(dirEntry.getDataOffset()));
         }
         else if (dirEntry.getTagName().equals(FileTag.HCFG.name()))
         {
            String value = getStringValueForDirectoryEntry(inByteSource, dirEntry);

            if (1 == dirEntry.getTagNum())
            {
               mInstrumentClass = value;
            }
            else if (2 == dirEntry.getTagNum())
            {
               mInstrumentFamily = value;
            }
            else if (3 == dirEntry.getTagNum())
            {
               mInstrumentName = value;
            }
            else if (4 == dirEntry.getTagNum())
            {
               mInstrumentParameters = value;
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.LANE.name()))
         {
            byte[] bytes = ByteUtil.getBytesFromInt(dirEntry.getDataOffset());
            mLane = ByteUtil.get2ByteInt(bytes, mByteOrder);
         }
         else if (dirEntry.getTagName().equals(FileTag.LIMS.name()))
         {
            mSampleTrackingID = getStringValueForDirectoryEntry(inByteSource, dirEntry);
         }
         else if (dirEntry.getTagName().equals(FileTag.MCHN.name()))
         {
            mMachineName = getStringValueForDirectoryEntry(inByteSource, dirEntry);
         }
         else if (dirEntry.getTagName().equals(FileTag.PBAS.name()))
         {
            inByteSource.seek(dirEntry.getDataOffset());
            String seqString = ByteUtil.getString(inByteSource, dirEntry.getDataSize());

            if (1 == dirEntry.getTagNum())
            {
               mUserSeq = seqString;
            }
            else if (2 == dirEntry.getTagNum())
            {
               mBasecallerSeq = seqString;
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.PCON.name()))
         {
            inByteSource.seek(dirEntry.getDataOffset());

            byte[] bytes = new byte[dirEntry.getDataSize()];
            inByteSource.read(bytes);

            // Converting the unsigned byte values to shorts for ease of handling
            short[] qualityScores = new short[dirEntry.getDataSize()];
            for (int i = 0; i < bytes.length; i++)
            {
               qualityScores[i] = (short) bytes[i];
            }

            if (1 == dirEntry.getTagNum())
            {
               mUserQualityScores = qualityScores;
            }
            else if (2 == dirEntry.getTagNum())
            {
               mBasecallerQualityScores = qualityScores;
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.phQL.name()))
         {
            mMaxQualityValue = (int) ByteUtil.getShort(ByteUtil.getBytesFromInt(dirEntry.getDataOffset()), 0, mByteOrder);
         }
         else if (dirEntry.getTagName().equals(FileTag.PLOC.name()))
         {
            inByteSource.seek(dirEntry.getDataOffset());
            short[] peakLocations = ByteUtil.getShortArray(inByteSource, dirEntry.getNumElements(), mByteOrder);

            if (1 == dirEntry.getTagNum())
            {
               mUserPeakLocations = peakLocations;
            }
            else if (2 == dirEntry.getTagNum())
            {
               mBasecallerPeakLocations = peakLocations;
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.QcRs.name()))
         {
            if (1 == dirEntry.getTagNum())
            {
               mQC_Warnings = getStringValueForDirectoryEntry(inByteSource, dirEntry);
            }
            else if (2 == dirEntry.getTagNum())
            {
               mQC_Errors = getStringValueForDirectoryEntry(inByteSource, dirEntry);
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.QV20.name()))
         {
            if (1 == dirEntry.getTagNum())
            {
               inByteSource.seek(dirEntry.getDataOffset());
               mQV20_Score = ByteUtil.getLong(inByteSource, mByteOrder);
            }
            else if (2 == dirEntry.getTagNum())
            {
               mQV20_Status = getStringValueForDirectoryEntry(inByteSource, dirEntry);
            }
         }
         else if (dirEntry.getTagName().equals(FileTag.RUND.name())
                  && 1 == dirEntry.getTagNum())
         {
            byte[] bytes = ByteUtil.getBytesFromInt(dirEntry.getDataOffset());
            int year = ByteUtil.get2ByteInt(bytes, 0, mByteOrder);
            int month = ByteUtil.get1ByteInt(bytes, 2);
            int day = ByteUtil.get1ByteInt(bytes, 3);

            Calendar cal = new GregorianCalendar();
            if (mRunDate != null)
            {
               cal.setTime(mRunDate);
            }

            cal.set(Calendar.YEAR, year);
            cal.set(Calendar.MONTH, month - 1);
            cal.set(Calendar.DAY_OF_MONTH, day);
            mRunDate = cal.getTime();
         }
         else if (dirEntry.getTagName().equals(FileTag.RUNT.name())
                  && 1 == dirEntry.getTagNum())
         {
            byte[] bytes = ByteUtil.getBytesFromInt(dirEntry.getDataOffset());
            int hour = ByteUtil.get1ByteInt(bytes, 0);
            int minute = ByteUtil.get1ByteInt(bytes, 1);
            int second = ByteUtil.get1ByteInt(bytes, 2);
            int hsecond = ByteUtil.get1ByteInt(bytes, 3);

            Calendar cal = new GregorianCalendar();
            if (mRunDate != null)
            {
               cal.setTime(mRunDate);
            }

            cal.set(Calendar.HOUR_OF_DAY, hour);
            cal.set(Calendar.MINUTE, minute);
            cal.set(Calendar.SECOND, second);
            mRunDate = cal.getTime();
         }
         else if (dirEntry.getTagName().equals(FileTag.SMPL.name()))
         {
            mSampleName = getStringValueForDirectoryEntry(inByteSource, dirEntry);
         }
      }

      // Now construct the trace data map
      for (int i = 0; i < 4; i++)
      {
         mTraceDataMap.put(Character.toUpperCase(mBaseOrder.charAt(i)), traceValueList.get(i));
      }
   }

   //---------------------------------------------------------------------------
   private String getStringValueForDirectoryEntry(ByteSource inByteSource, DirectoryEntry inDirEntry)
      throws IOException
   {
      String value;
      if (inDirEntry.getDataSize() <= 4)
      {
         value = new String(ByteUtil.getBytesFromInt(inDirEntry.getDataOffset()));
         int zeroIndex = value.indexOf(0);
         if (zeroIndex >= 0)
         {
            value = value.substring(0, zeroIndex);
         }
      }
      else
      {
         inByteSource.seek(inDirEntry.getDataOffset());
         value = ByteUtil.getString(inByteSource, inDirEntry.getDataSize());
      }

      // Pascal type string with the lenght as the first byte?
      if (18 == inDirEntry.getElementType()
          && value.length() > 1)
      {
         value = value.substring(1);
      }

      return value;
   }

   //###########################################################################
   // INNER CLASS
   //###########################################################################

   private class DirectoryEntry
   {
      private String mTagName;
      private int    mTagNum;
      private int    mElementType;
      private int    mElementSize;
      private int    mNumElements;
      private int    mDataSize;
      private int    mDataOffset;
      private int    mDataHandle;

      //------------------------------------------------------------------------
      public DirectoryEntry(ByteSource inByteSource)
         throws IOException
      {
         mTagName = ByteUtil.getString(inByteSource, 4);
         mTagNum = ByteUtil.getInt(inByteSource, mByteOrder);
         mElementType = ByteUtil.get2ByteInt(inByteSource, mByteOrder);
         mElementSize = ByteUtil.get2ByteInt(inByteSource, mByteOrder);
         mNumElements = ByteUtil.getInt(inByteSource, mByteOrder);
         mDataSize = ByteUtil.getInt(inByteSource, mByteOrder);
         mDataOffset = ByteUtil.getInt(inByteSource, mByteOrder);
         mDataHandle = ByteUtil.getInt(inByteSource, mByteOrder);
      }

      //------------------------------------------------------------------------
      public String getTagName()
      {
         return mTagName;
      }

      //------------------------------------------------------------------------
      public int getTagNum()
      {
         return mTagNum;
      }

      //------------------------------------------------------------------------
      public int getElementType()
      {
         return mElementType;
      }

      //------------------------------------------------------------------------
      public int getNumElements()
      {
         return mNumElements;
      }

      //------------------------------------------------------------------------
      public int getDataSize()
      {
         return mDataSize;
      }

      //------------------------------------------------------------------------
      public int getDataOffset()
      {
         return mDataOffset;
      }

      //------------------------------------------------------------------------
      @Override
      public String toString()
      {
         return mTagName;
      }

   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy