/**
  * Converts argument to DoubleCell if it does not fully support the DataValue interfaces supported
  * by DoubleCell.TYPE .
  *
  * @param cell Cell to convert (or not)
  * @return The argument or a new DoubleCell.
  */
 private DataCell convertToDoubleCellIfNecessary(final DataCell cell) {
   if (cell.isMissing()) {
     return DataType.getMissingCell();
   }
   if (TYPE.isASuperTypeOf(cell.getType())) {
     return cell;
   }
   return new DoubleCell(((DoubleValue) cell).getDoubleValue());
 }
  @Override
  protected DataTableSpec[] configure(DataTableSpec[] inSpecs) throws InvalidSettingsException {
    DataType type = inSpecs[0].iterator().next().getType();
    if (type.isCollectionType()) {
      throw new InvalidSettingsException("collection type at port 0 detected");
    }

    outspec = getOutSpec(type);

    return outspec;
  }
 /** {@inheritDoc} */
 @Override
 protected DataCell getResultInternal() {
   if (m_cells.size() == 0) {
     return DataType.getMissingCell();
   }
   if (m_cells.size() == 1) {
     return convertToDoubleCellIfNecessary(m_cells.get(0));
   }
   Collections.sort(m_cells, m_comparator);
   final double middle = m_cells.size() / 2.0;
   if (middle > (int) middle) {
     return convertToDoubleCellIfNecessary(m_cells.get((int) middle));
   }
   // the list is even return the middle two
   final double val1 = ((DoubleValue) m_cells.get((int) middle - 1)).getDoubleValue();
   final double val2 = ((DoubleValue) m_cells.get((int) middle)).getDoubleValue();
   return new DoubleCell((val1 + val2) / 2);
 }
  @Override
  protected void updateComponent() {
    if (beforeLoad) { // This is immediately *after* load!
      newColumns.clear();
      newColumns.addAll(
          Arrays.asList(((SettingsModelStringArray) getModel()).getStringArrayValue()));
      beforeLoad = false; // Never again
    }
    final PortObjectSpec lastTableSpec = getLastTableSpec(portIndex);
    final List<DataRow> dataRows = new ArrayList<DataRow>();
    final List<DataColumnSpec> colSpecs = new ArrayList<DataColumnSpec>();
    if (lastTableSpec instanceof DataTableSpec) {
      final DataTableSpec spec = (DataTableSpec) lastTableSpec;
      try {
        final Map<List<String>, Map<String, Integer>> parts =
            UnpivotNodeModel.createParts(pattern.getStringValue(), spec);
        final Set<Integer> participating = new HashSet<Integer>();
        for (final Map<String, Integer> map : parts.values()) {
          for (final Integer i : map.values()) {
            participating.add(i);
          }
        }
        for (int i = 0; i < spec.getNumColumns(); ++i) {
          if (!participating.contains(Integer.valueOf(i))) {
            colSpecs.add(spec.getColumnSpec(i));
          }
        }
        singleCount = colSpecs.size();
        int rowCount = 0;
        if (parts.isEmpty() || newColumns.size() != parts.keySet().iterator().next().size()) {
          newColumns.clear();
          if (!parts.isEmpty()) { // Fill with defaults
            for (int i = parts.keySet().iterator().next().size(); i-- > 0; ) {
              final String colName = "Col_" + i;
              newColumns.add(colName);
              colSpecs.add(new DataColumnSpecCreator(colName, StringCell.TYPE).createSpec());
            }
          }
        } else {
          for (final String colName : newColumns) {
            colSpecs.add(new DataColumnSpecCreator(colName, StringCell.TYPE).createSpec());
          }
        }
        final Map<String, DataType> types = new LinkedHashMap<String, DataType>();
        for (final Entry<List<String>, Map<String, Integer>> outer : parts.entrySet()) {
          final Map<String, Integer> map = outer.getValue();
          for (final Entry<String, Integer> entry : map.entrySet()) {
            final String colName = entry.getKey();
            final DataType origType = types.get(colName);
            if (origType == null) {
              types.put(colName, spec.getColumnSpec(entry.getValue().intValue()).getType());
            } else {
              types.put(
                  colName,
                  DataType.getCommonSuperType(
                      origType, spec.getColumnSpec(entry.getValue().intValue()).getType()));
            }
          }
        }
        for (final Entry<String, DataType> entry : types.entrySet()) {
          colSpecs.add(new DataColumnSpecCreator(entry.getKey(), entry.getValue()).createSpec());
        }
        for (final Entry<List<String>, Map<String, Integer>> entry : parts.entrySet()) {
          final List<DataCell> cells = new ArrayList<DataCell>(colSpecs.size());
          for (int i = singleCount; i-- > 0; ) {
            cells.add(DataType.getMissingCell());
          }
          for (final String val : entry.getKey()) {
            cells.add(new StringCell(val));
          }
          for (final Entry<String, Integer> inner : entry.getValue().entrySet()) {
            final DataColumnDomain domain =
                spec.getColumnSpec(inner.getValue().intValue()).getDomain();
            cells.add(
                domain.getLowerBound() == null
                    ? domain.getValues() == null || domain.getValues().isEmpty()
                        ? DataType.getMissingCell()
                        : domain.getValues().iterator().next()
                    : domain.getLowerBound());
          }
          dataRows.add(new DefaultRow("Row" + rowCount, cells));
          ++rowCount;
        }
      } catch (final PatternSyntaxException e) {
        for (final DataColumnSpec dataColumnSpec : spec) {
          colSpecs.add(dataColumnSpec);
        }
      }
    }

    @SuppressWarnings("deprecation")
    final DataTable data =
        new org.knime.core.data.def.DefaultTable(
            dataRows.toArray(new DataRow[dataRows.size()]),
            new DataTableSpec(colSpecs.toArray(new DataColumnSpec[colSpecs.size()])));
    table.setDataTable(data);
    table.repaint();
  }
public class MADOperator extends AggregationOperator {

  private static final DataType TYPE = DoubleCell.TYPE;

  private final List<DataCell> m_cells;
  private final Comparator<DataCell> m_comparator = TYPE.getComparator();

  public MADOperator(
      OperatorData operatorData,
      GlobalSettings globalSettings,
      OperatorColumnSettings opColSettings) {
    super(operatorData, globalSettings, opColSettings);
    // TODO Auto-generated constructor stub
    try {
      m_cells = new ArrayList<DataCell>(getMaxUniqueValues());
    } catch (final OutOfMemoryError e) {
      throw new IllegalArgumentException("Maximum unique values number too big");
    }
  }

  /**
   * Constructor for class MedianOperator.
   *
   * @param globalSettings the global settings
   * @param opColSettings the operator column specific settings
   */
  public MADOperator(
      final GlobalSettings globalSettings, final OperatorColumnSettings opColSettings) {
    this(
        new OperatorData("MAD", true, false, DoubleValue.class, false),
        globalSettings,
        setInclMissingFlag(opColSettings));
  }

  public MADOperator() {
    this(
        new OperatorData("MAD", false, false, DoubleValue.class, false),
        new GlobalSettings(0),
        new OperatorColumnSettings(false, null));
  }

  /**
   * Ensure that the flag is set correctly since this method does not support changing of the
   * missing cell handling option.
   *
   * @param opColSettings the {@link OperatorColumnSettings} to set
   * @return the correct {@link OperatorColumnSettings}
   */
  private static OperatorColumnSettings setInclMissingFlag(
      final OperatorColumnSettings opColSettings) {
    opColSettings.setInclMissing(false);
    return opColSettings;
  }

  /** {@inheritDoc} */
  @Override
  protected DataType getDataType(final DataType origType) {
    return TYPE;
  }

  /** {@inheritDoc} */
  @Override
  protected boolean computeInternal(final DataCell cell) {
    if (cell.isMissing()) {
      return false;
    }
    if (m_cells.size() >= getMaxUniqueValues()) {
      return true;
    }
    m_cells.add(cell);
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public AggregationOperator createInstance(
      final GlobalSettings globalSettings, final OperatorColumnSettings opColSettings) {
    return new MedianOperator(globalSettings, opColSettings);
  }

  /** {@inheritDoc} */
  @Override
  protected DataCell getResultInternal() {
    if (m_cells.size() == 0) {
      return DataType.getMissingCell();
    }
    if (m_cells.size() == 1) {
      return convertToDoubleCellIfNecessary(m_cells.get(0));
    }
    Collections.sort(m_cells, m_comparator);
    final double middle = m_cells.size() / 2.0;
    if (middle > (int) middle) {
      return convertToDoubleCellIfNecessary(m_cells.get((int) middle));
    }
    // the list is even return the middle two
    final double val1 = ((DoubleValue) m_cells.get((int) middle - 1)).getDoubleValue();
    final double val2 = ((DoubleValue) m_cells.get((int) middle)).getDoubleValue();
    return new DoubleCell((val1 + val2) / 2);
  }

  /**
   * Converts argument to DoubleCell if it does not fully support the DataValue interfaces supported
   * by DoubleCell.TYPE .
   *
   * @param cell Cell to convert (or not)
   * @return The argument or a new DoubleCell.
   */
  private DataCell convertToDoubleCellIfNecessary(final DataCell cell) {
    if (cell.isMissing()) {
      return DataType.getMissingCell();
    }
    if (TYPE.isASuperTypeOf(cell.getType())) {
      return cell;
    }
    return new DoubleCell(((DoubleValue) cell).getDoubleValue());
  }

  /** {@inheritDoc} */
  @Override
  protected void resetInternal() {
    m_cells.clear();
  }

  /** {@inheritDoc} */
  @Override
  public String getDescription() {
    return "Calculates the median of a list of numbers. " + "Missing cells are skipped.";
  }
}
  @Override
  protected BufferedDataTable[] execute(BufferedDataTable[] inData, ExecutionContext exec)
      throws Exception {

    List<File> inputFiles =
        FileSelectPanel.getInputFiles(propInputDir.getStringValue(), getAllowedFileExtensions());

    if (inputFiles.isEmpty()) {
      throw new RuntimeException("No files selected");
    }

    // first group files into plate-groups
    Map<String, List<File>> plateFiles = splitFilesIntoPlates(inputFiles);

    if (inputFiles.isEmpty()) {
      throw new RuntimeException("No valid envision-files in selection " + inputFiles);
    }

    // split files
    List<String> allAttributes = mergeAttributes(plateFiles);
    List<Attribute> colAttributes = compileColumnModel(allAttributes);

    DataTableSpec outputSpec = AttributeUtils.compileTableSpecs(colAttributes);
    BufferedDataContainer container = exec.createDataContainer(outputSpec);

    // populate the table
    int fileCounter = 0, rowCounter = 0;
    for (String barcode : plateFiles.keySet()) {

      logger.info("Processing plate " + barcode);

      Plate plate = new Plate();

      // invalidate plate-dims as these become fixed in the loop
      plate.setNumColumns(-1);
      plate.setNumRows(-1);

      for (File file : plateFiles.get(barcode)) {
        String attributeName = getAttributeNameOfEnvisionFile(file);
        parseFile(plate, attributeName, file);

        BufTableUtils.updateProgress(exec, fileCounter++, inputFiles.size());
      }

      // now create the data-rows for this table
      for (Well well : plate.getWells()) {
        if (well.getReadOutNames().isEmpty()) {
          continue;
        }

        DataCell[] knimeRow = new DataCell[colAttributes.size()];

        // first add the barcode-column
        knimeRow[0] = new StringCell(barcode);

        knimeRow[1] = colAttributes.get(1).createCell(well.getPlateRow());
        knimeRow[2] = colAttributes.get(2).createCell(well.getPlateColumn());

        for (String attributeName : allAttributes) {
          int rowIndex = allAttributes.indexOf(attributeName);
          Double value = well.getReadout(attributeName);

          if (value != null) {
            knimeRow[3 + rowIndex] = new DoubleCell(value);
          } else {
            knimeRow[3 + rowIndex] = DataType.getMissingCell();
          }
        }

        DataRow tableRow = new DefaultRow(new RowKey("" + rowCounter++), knimeRow);
        container.addRowToTable(tableRow);
      }
    }

    container.close();

    return new BufferedDataTable[] {container.getTable()};
  }