/**
   * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
   *     java.lang.String)
   */
  public void endElement(String namespaceURI, String sName, String qName) throws SAXException {

    if (canceled) throw new SAXException("Parsing canceled");

    // <NAME>
    if (qName.equals(PeakListElementName_2_3.PEAKLIST_NAME.getElementName())) {
      name = getTextOfElement();
      logger.info("Loading peak list: " + name);
      peakListName = name;
    }

    // <PEAKLIST_DATE>
    if (qName.equals(PeakListElementName_2_3.PEAKLIST_DATE.getElementName())) {
      dateCreated = getTextOfElement();
    }

    // <QUANTITY>
    if (qName.equals(PeakListElementName_2_3.QUANTITY.getElementName())) {
      String text = getTextOfElement();
      totalRows = Integer.parseInt(text);
    }

    // <RAW_FILE>
    if (qName.equals(PeakListElementName_2_3.RAWFILE.getElementName())) {
      rawDataFileID = getTextOfElement();
      RawDataFile dataFile = dataFilesIDMap.get(rawDataFileID);
      if (dataFile == null) {
        throw new SAXException(
            "Cannot open peak list, because raw data file " + rawDataFileID + " is missing.");
      }
      currentPeakListDataFiles.add(dataFile);
    }

    // <SCAN_ID>
    if (qName.equals(PeakListElementName_2_3.SCAN_ID.getElementName())) {

      byte[] bytes = Base64.decodeToBytes(getTextOfElement());
      // make a data input stream
      DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes));
      scanNumbers = new int[numOfMZpeaks];
      for (int i = 0; i < numOfMZpeaks; i++) {
        try {
          scanNumbers[i] = dataInputStream.readInt();
        } catch (IOException ex) {
          throw new SAXException(ex);
        }
      }
    }

    // <REPRESENTATIVE_SCAN>
    if (qName.equals(PeakListElementName_2_3.REPRESENTATIVE_SCAN.getElementName())) {
      representativeScan = Integer.valueOf(getTextOfElement());
    }

    // <FRAGMENT_SCAN>

    if (qName.equals(PeakListElementName_2_3.FRAGMENT_SCAN.getElementName())) {
      fragmentScan = Integer.valueOf(getTextOfElement());
    }

    // <MASS>
    if (qName.equals(PeakListElementName_2_3.MZ.getElementName())) {

      byte[] bytes = Base64.decodeToBytes(getTextOfElement());
      // make a data input stream
      DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes));
      masses = new double[numOfMZpeaks];
      for (int i = 0; i < numOfMZpeaks; i++) {
        try {
          masses[i] = (double) dataInputStream.readFloat();
        } catch (IOException ex) {
          throw new SAXException(ex);
        }
      }
    }

    // <HEIGHT>
    if (qName.equals(PeakListElementName_2_3.HEIGHT.getElementName())) {

      byte[] bytes = Base64.decodeToBytes(getTextOfElement());
      // make a data input stream
      DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes));
      intensities = new double[numOfMZpeaks];
      for (int i = 0; i < numOfMZpeaks; i++) {
        try {
          intensities[i] = (double) dataInputStream.readFloat();
        } catch (IOException ex) {
          throw new SAXException(ex);
        }
      }
    }

    // <PEAK>
    if (qName.equals(PeakListElementName_2_3.PEAK.getElementName())) {

      DataPoint[] mzPeaks = new DataPoint[numOfMZpeaks];
      Range peakRTRange = null, peakMZRange = null, peakIntensityRange = null;
      RawDataFile dataFile = dataFilesIDMap.get(peakColumnID);

      if (dataFile == null)
        throw new SAXException("Error in project: data file " + peakColumnID + " not found");

      for (int i = 0; i < numOfMZpeaks; i++) {

        Scan sc = dataFile.getScan(scanNumbers[i]);
        double retentionTime = sc.getRetentionTime();

        double mz = masses[i];
        double intensity = intensities[i];

        if ((peakRTRange == null) || (peakIntensityRange == null)) {
          peakRTRange = new Range(retentionTime);
          peakIntensityRange = new Range(intensity);
        } else {
          peakRTRange.extendRange(retentionTime);
          peakIntensityRange.extendRange(intensity);
        }
        if (mz > 0.0) {
          mzPeaks[i] = new SimpleDataPoint(mz, intensity);
          if (peakMZRange == null) peakMZRange = new Range(mz);
          else peakMZRange.extendRange(mz);
        }
      }

      FeatureStatus status = FeatureStatus.valueOf(peakStatus);

      SimpleFeature peak =
          new SimpleFeature(
              dataFile,
              mass,
              rt,
              height,
              area,
              scanNumbers,
              mzPeaks,
              status,
              representativeScan,
              fragmentScan,
              peakRTRange,
              peakMZRange,
              peakIntensityRange);

      peak.setCharge(currentPeakCharge);

      if (currentIsotopes.size() > 0) {
        SimpleIsotopePattern newPattern =
            new SimpleIsotopePattern(
                currentIsotopes.toArray(new DataPoint[0]),
                currentIsotopePatternStatus,
                currentIsotopePatternDescription);
        peak.setIsotopePattern(newPattern);
        currentIsotopes.clear();
      }

      buildingRow.addPeak(dataFile, peak);
    }

    // <IDENTITY_PROPERTY>
    if (qName.equals(PeakListElementName_2_3.IDPROPERTY.getElementName())) {
      identityProperties.put(identityPropertyName, getTextOfElement());
    }

    // <PEAK_IDENTITY>
    if (qName.equals(PeakListElementName_2_3.PEAK_IDENTITY.getElementName())) {
      SimplePeakIdentity identity = new SimplePeakIdentity(identityProperties);
      buildingRow.addPeakIdentity(identity, preferred);
    }

    // <ROW>
    if (qName.equals(PeakListElementName_2_3.ROW.getElementName())) {
      buildingPeakList.addRow(buildingRow);
      buildingRow = null;
      parsedRows++;
    }

    // <ISOTOPE>
    if (qName.equals(PeakListElementName_2_3.ISOTOPE.getElementName())) {
      String text = getTextOfElement();
      String items[] = text.split(":");
      double mz = Double.valueOf(items[0]);
      double intensity = Double.valueOf(items[1]);
      DataPoint isotope = new SimpleDataPoint(mz, intensity);
      currentIsotopes.add(isotope);
    }

    if (qName.equals(PeakListElementName_2_3.METHOD_NAME.getElementName())) {
      String appliedMethod = getTextOfElement();
      appliedMethods.add(appliedMethod);
    }

    if (qName.equals(PeakListElementName_2_3.METHOD_PARAMETERS.getElementName())) {
      String appliedMethodParam = getTextOfElement();
      appliedMethodParameters.add(appliedMethodParam);
    }
  }
  /** @see Runnable#run() */
  public void run() {

    setStatus(TaskStatus.PROCESSING);

    logger.info("Started chromatogram builder on " + dataFile);

    scans = scanSelection.getMatchingScans(dataFile);
    int allScanNumbers[] = scanSelection.getMatchingScanNumbers(dataFile);
    totalScans = scans.length;

    // Check if the scans are properly ordered by RT
    double prevRT = Double.NEGATIVE_INFINITY;
    for (Scan s : scans) {
      if (s.getRetentionTime() < prevRT) {
        setStatus(TaskStatus.ERROR);
        final String msg =
            "Retention time of scan #"
                + s.getScanNumber()
                + " is smaller then the retention time of the previous scan."
                + " Please make sure you only use scans with increasing retention times."
                + " You can restrict the scan numbers in the parameters, or you can use the Crop filter module";
        setErrorMessage(msg);
        return;
      }
      prevRT = s.getRetentionTime();
    }

    // Create new peak list
    newPeakList = new SimplePeakList(dataFile + " " + suffix, dataFile);

    Chromatogram[] chromatograms;
    HighestDataPointConnector massConnector =
        new HighestDataPointConnector(
            dataFile, allScanNumbers, minimumTimeSpan, minimumHeight, mzTolerance);

    for (Scan scan : scans) {

      if (isCanceled()) return;

      MassList massList = scan.getMassList(massListName);
      if (massList == null) {
        setStatus(TaskStatus.ERROR);
        setErrorMessage(
            "Scan "
                + dataFile
                + " #"
                + scan.getScanNumber()
                + " does not have a mass list "
                + massListName);
        return;
      }

      DataPoint mzValues[] = massList.getDataPoints();

      if (mzValues == null) {
        setStatus(TaskStatus.ERROR);
        setErrorMessage(
            "Mass list "
                + massListName
                + " does not contain m/z values for scan #"
                + scan.getScanNumber()
                + " of file "
                + dataFile);
        return;
      }

      massConnector.addScan(scan.getScanNumber(), mzValues);
      processedScans++;
    }

    chromatograms = massConnector.finishChromatograms();

    // Sort the final chromatograms by m/z
    Arrays.sort(chromatograms, new PeakSorter(SortingProperty.MZ, SortingDirection.Ascending));

    // Add the chromatograms to the new peak list
    for (Feature finishedPeak : chromatograms) {
      SimplePeakListRow newRow = new SimplePeakListRow(newPeakID);
      newPeakID++;
      newRow.addPeak(dataFile, finishedPeak);
      newPeakList.addRow(newRow);
    }

    // Add new peaklist to the project
    project.addPeakList(newPeakList);

    // Add quality parameters to peaks
    QualityParameters.calculateQualityParameters(newPeakList);

    setStatus(TaskStatus.FINISHED);

    logger.info("Finished chromatogram builder on " + dataFile);
  }
  /**
   * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
   *     java.lang.String, org.xml.sax.Attributes)
   */
  public void startElement(String namespaceURI, String lName, String qName, Attributes attrs)
      throws SAXException {

    if (canceled) throw new SAXException("Parsing canceled");

    // <ROW>
    if (qName.equals(PeakListElementName_2_3.ROW.getElementName())) {

      if (buildingPeakList == null) {
        initializePeakList();
      }
      int rowID = Integer.parseInt(attrs.getValue(PeakListElementName_2_3.ID.getElementName()));
      buildingRow = new SimplePeakListRow(rowID);
      String comment = attrs.getValue(PeakListElementName_2_3.COMMENT.getElementName());
      buildingRow.setComment(comment);
    }

    // <PEAK_IDENTITY>
    if (qName.equals(PeakListElementName_2_3.PEAK_IDENTITY.getElementName())) {
      identityProperties = new Hashtable<String, String>();
      preferred =
          Boolean.parseBoolean(attrs.getValue(PeakListElementName_2_3.PREFERRED.getElementName()));
    }

    // <IDENTITY_PROPERTY>
    if (qName.equals(PeakListElementName_2_3.IDPROPERTY.getElementName())) {
      identityPropertyName = attrs.getValue(PeakListElementName_2_3.NAME.getElementName());
    }

    // <PEAK>
    if (qName.equals(PeakListElementName_2_3.PEAK.getElementName())) {

      peakColumnID = attrs.getValue(PeakListElementName_2_3.COLUMN.getElementName());
      mass = Double.parseDouble(attrs.getValue(PeakListElementName_2_3.MZ.getElementName()));
      // Before MZmine 2.6 retention time was saved in seconds, but now we
      // use minutes, so we need to divide by 60
      rt = Double.parseDouble(attrs.getValue(PeakListElementName_2_3.RT.getElementName())) / 60d;
      height = Double.parseDouble(attrs.getValue(PeakListElementName_2_3.HEIGHT.getElementName()));
      area = Double.parseDouble(attrs.getValue(PeakListElementName_2_3.AREA.getElementName()));
      peakStatus = attrs.getValue(PeakListElementName_2_3.STATUS.getElementName());
      String chargeString = attrs.getValue(PeakListElementName_2_3.CHARGE.getElementName());
      if (chargeString != null) currentPeakCharge = Integer.valueOf(chargeString);
      else currentPeakCharge = 0;
    }

    // <MZPEAK>
    if (qName.equals(PeakListElementName_2_3.MZPEAKS.getElementName())) {
      numOfMZpeaks =
          Integer.parseInt(attrs.getValue(PeakListElementName_2_3.QUANTITY.getElementName()));
    }

    // <ISOTOPE_PATTERN>
    if (qName.equals(PeakListElementName_2_3.ISOTOPE_PATTERN.getElementName())) {
      currentIsotopes.clear();
      currentIsotopePatternStatus =
          IsotopePatternStatus.valueOf(
              attrs.getValue(PeakListElementName_2_3.STATUS.getElementName()));
      currentIsotopePatternDescription =
          attrs.getValue(PeakListElementName_2_3.DESCRIPTION.getElementName());
    }
  }