<commit_before>/*
 * Copyright 2006-2021 The MZmine Development Team
 *
 * This file is part of MZmine.
 *
 * MZmine is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * MZmine 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with MZmine; if not,
 * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

package io.github.mzmine.project.impl;

import io.github.mzmine.datamodel.MZmineProject;
import io.github.mzmine.datamodel.RawDataFile;
import io.github.mzmine.datamodel.features.FeatureList;
import io.github.mzmine.datamodel.features.ModularFeatureList;
import io.github.mzmine.main.MZmineCore;
import io.github.mzmine.modules.tools.rawfilerename.RawDataFileRenameModule;
import io.github.mzmine.parameters.UserParameter;
import io.github.mzmine.util.javafx.FxThreadUtil;
import java.io.File;
import java.util.Hashtable;
import java.util.List;
import java.util.Objects;
import java.util.Vector;
import java.util.logging.Logger;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ButtonType;
import org.jetbrains.annotations.Nullable;

/**
 * This class represents a MZmine project. That includes raw data files, feature lists and
 * parameters.
 */
public class MZmineProjectImpl implements MZmineProject {

  private final SimpleListProperty<RawDataFile> rawDataFilesProperty = //
      new SimpleListProperty<>(//
          FXCollections.synchronizedObservableList(//
              FXCollections.observableArrayList()//
          ));
  private final SimpleListProperty<FeatureList> featureListsProperty = //
      new SimpleListProperty<>(//
          FXCollections.synchronizedObservableList(//
              FXCollections.observableArrayList()//
          ));
  private Logger logger = Logger.getLogger(this.getClass().getName());
  private Hashtable<UserParameter<?, ?>, Hashtable<RawDataFile, Object>> projectParametersAndValues;
  private File projectFile;

  @Nullable
  private Boolean standalone;

  /*
   * private Collection<MZmineProjectListener> listeners = Collections.synchronizedCollection(new
   * LinkedList<MZmineProjectListener>());
   */
  public MZmineProjectImpl() {
    projectParametersAndValues = new Hashtable<>();
  }

  public static String getUniqueName(String proposedName, List<String> existingNames) {
    int i = 1;

    proposedName = proposedName.trim().replaceAll("\\([\\d]+\\)$", "").trim();

    String unique = proposedName + " (" + i + ")";
    while (existingNames.contains(unique)) {
      i++;
      unique = proposedName + " (" + i + ")";
    }
    return unique;
  }

  @Override
  public Hashtable<UserParameter<?, ?>, Hashtable<RawDataFile, Object>> getProjectParametersAndValues() {
    return projectParametersAndValues;
  }

  @Override
  public void setProjectParametersAndValues(
      Hashtable<UserParameter<?, ?>, Hashtable<RawDataFile, Object>> projectParametersAndValues) {
    this.projectParametersAndValues = projectParametersAndValues;
  }

  @Nullable
  @Override
  public FeatureList getFeatureList(String name) {
    ObservableList<FeatureList> lists = getFeatureLists();
    // find last with name
    for (int i = lists.size() - 1; i >= 0; i--) {
      if (lists.get(i).getName().equals(name)) {
        return lists.get(i);
      }
    }
    return null;
  }

  @Override
  public void addParameter(UserParameter<?, ?> parameter) {
    if (projectParametersAndValues.containsKey(parameter)) {
      return;
    }

    Hashtable<RawDataFile, Object> parameterValues = new Hashtable<>();
    projectParametersAndValues.put(parameter, parameterValues);

  }

  @Override
  public void removeParameter(UserParameter<?, ?> parameter) {
    projectParametersAndValues.remove(parameter);
  }

  @Override
  public UserParameter<?, ?> getParameterByName(String name) {
    for (UserParameter<?, ?> parameter : getParameters()) {
      if (parameter.getName().equals(name)) {
        return parameter;
      }
    }
    return null;
  }

  @Override
  public boolean hasParameter(UserParameter<?, ?> parameter) {
    // matching by name
    UserParameter<?, ?> param = getParameterByName(parameter.getName());
    if (param == null) {
      return false;
    }
    return true;
  }

  @Override
  public UserParameter<?, ?>[] getParameters() {
    return projectParametersAndValues.keySet().toArray(new UserParameter[0]);
  }

  @Override
  public void setParameterValue(UserParameter<?, ?> parameter, RawDataFile rawDataFile,
      Object value) {
    if (!(hasParameter(parameter))) {
      addParameter(parameter);
    }
    Hashtable<RawDataFile, Object> parameterValues = projectParametersAndValues.get(parameter);
    if (value == null) {
      parameterValues.remove(rawDataFile);
    } else {
      parameterValues.put(rawDataFile, value);
    }
  }

  @Override
  public Object getParameterValue(UserParameter<?, ?> parameter, RawDataFile rawDataFile) {
    if (!(hasParameter(parameter))) {
      return null;
    }
    Object value = projectParametersAndValues.get(parameter).get(rawDataFile);

    return value;
  }

  @Override
  public void addFile(final RawDataFile newFile) {

    assert newFile != null;

    synchronized (rawDataFilesProperty.get()) {
      // avoid duplicate file names and check the actual names of the files of the raw data files
      // since that will be the problem during project save (duplicate zip entries)
      final List<String> names = rawDataFilesProperty.get().stream()
          .map(RawDataFile::getAbsolutePath).filter(Objects::nonNull).map(File::new)
          .map(File::getName).toList();
      if (names.contains(newFile.getName())) {
        if (!MZmineCore.isHeadLessMode()) {
          final ButtonType buttonType = MZmineCore.getDesktop().displayConfirmation(
              "WARNING: A raw data file with the same name already exists\nin the project. "
                  + "This will lead to errors during project saving.\nDo you wish to continue?",
              ButtonType.YES, ButtonType.NO);
          if (buttonType == ButtonType.NO) {
            return;
          }
          // this will actually rename the file that is currently in the project, but it does the
          // job, since this case is not properly supported.
          RawDataFileRenameModule.renameFile(newFile, newFile.getName(), false);
        } else {
          logger.warning("Cannot at raw data file with a name that already exists in project.");
          return;
        }
      }
    }

    logger.finest("Adding a new file to the project: " + newFile.getName());

    FxThreadUtil.runOnFxThreadAndWait(() -> {
      rawDataFilesProperty.get().add(newFile);<commit_msg>prohibit addition of duplicate files<commit_after>/*
 * Copyright 2006-2021 The MZmine Development Team
 *
 * This file is part of MZmine.
 *
 * MZmine is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * MZmine 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with MZmine; if not,
 * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

package io.github.mzmine.project.impl;

import io.github.mzmine.datamodel.MZmineProject;
import io.github.mzmine.datamodel.RawDataFile;
import io.github.mzmine.datamodel.features.FeatureList;
import io.github.mzmine.datamodel.features.ModularFeatureList;
import io.github.mzmine.main.MZmineCore;
import io.github.mzmine.parameters.UserParameter;
import io.github.mzmine.util.javafx.FxThreadUtil;
import java.io.File;
import java.util.Hashtable;
import java.util.List;
import java.util.Objects;
import java.util.Vector;
import java.util.logging.Logger;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.jetbrains.annotations.Nullable;

/**
 * This class represents a MZmine project. That includes raw data files, feature lists and
 * parameters.
 */
public class MZmineProjectImpl implements MZmineProject {

  private final SimpleListProperty<RawDataFile> rawDataFilesProperty = //
      new SimpleListProperty<>(//
          FXCollections.synchronizedObservableList(//
              FXCollections.observableArrayList()//
          ));
  private final SimpleListProperty<FeatureList> featureListsProperty = //
      new SimpleListProperty<>(//
          FXCollections.synchronizedObservableList(//
              FXCollections.observableArrayList()//
          ));
  private Logger logger = Logger.getLogger(this.getClass().getName());
  private Hashtable<UserParameter<?, ?>, Hashtable<RawDataFile, Object>> projectParametersAndValues;
  private File projectFile;

  @Nullable
  private Boolean standalone;

  /*
   * private Collection<MZmineProjectListener> listeners = Collections.synchronizedCollection(new
   * LinkedList<MZmineProjectListener>());
   */
  public MZmineProjectImpl() {
    projectParametersAndValues = new Hashtable<>();
  }

  public static String getUniqueName(String proposedName, List<String> existingNames) {
    int i = 1;

    proposedName = proposedName.trim().replaceAll("\\([\\d]+\\)$", "").trim();

    String unique = proposedName + " (" + i + ")";
    while (existingNames.contains(unique)) {
      i++;
      unique = proposedName + " (" + i + ")";
    }
    return unique;
  }

  @Override
  public Hashtable<UserParameter<?, ?>, Hashtable<RawDataFile, Object>> getProjectParametersAndValues() {
    return projectParametersAndValues;
  }

  @Override
  public void setProjectParametersAndValues(
      Hashtable<UserParameter<?, ?>, Hashtable<RawDataFile, Object>> projectParametersAndValues) {
    this.projectParametersAndValues = projectParametersAndValues;
  }

  @Nullable
  @Override
  public FeatureList getFeatureList(String name) {
    ObservableList<FeatureList> lists = getFeatureLists();
    // find last with name
    for (int i = lists.size() - 1; i >= 0; i--) {
      if (lists.get(i).getName().equals(name)) {
        return lists.get(i);
      }
    }
    return null;
  }

  @Override
  public void addParameter(UserParameter<?, ?> parameter) {
    if (projectParametersAndValues.containsKey(parameter)) {
      return;
    }

    Hashtable<RawDataFile, Object> parameterValues = new Hashtable<>();
    projectParametersAndValues.put(parameter, parameterValues);

  }

  @Override
  public void removeParameter(UserParameter<?, ?> parameter) {
    projectParametersAndValues.remove(parameter);
  }

  @Override
  public UserParameter<?, ?> getParameterByName(String name) {
    for (UserParameter<?, ?> parameter : getParameters()) {
      if (parameter.getName().equals(name)) {
        return parameter;
      }
    }
    return null;
  }

  @Override
  public boolean hasParameter(UserParameter<?, ?> parameter) {
    // matching by name
    UserParameter<?, ?> param = getParameterByName(parameter.getName());
    if (param == null) {
      return false;
    }
    return true;
  }

  @Override
  public UserParameter<?, ?>[] getParameters() {
    return projectParametersAndValues.keySet().toArray(new UserParameter[0]);
  }

  @Override
  public void setParameterValue(UserParameter<?, ?> parameter, RawDataFile rawDataFile,
      Object value) {
    if (!(hasParameter(parameter))) {
      addParameter(parameter);
    }
    Hashtable<RawDataFile, Object> parameterValues = projectParametersAndValues.get(parameter);
    if (value == null) {
      parameterValues.remove(rawDataFile);
    } else {
      parameterValues.put(rawDataFile, value);
    }
  }

  @Override
  public Object getParameterValue(UserParameter<?, ?> parameter, RawDataFile rawDataFile) {
    if (!(hasParameter(parameter))) {
      return null;
    }
    Object value = projectParametersAndValues.get(parameter).get(rawDataFile);

    return value;
  }

  @Override
  public synchronized void addFile(final RawDataFile newFile) {

    assert newFile != null;

    // avoid duplicate file names and check the actual names of the files of the raw data files
    // since that will be the problem during project save (duplicate zip entries)
    final List<String> names = rawDataFilesProperty.get().stream().map(RawDataFile::getAbsolutePath)
        .filter(Objects::nonNull).map(File::new).map(File::getName).toList();
    // if there is no path, it is an artificially created file (e.g. by a module) so it does not matter
    final String name =
        newFile.getAbsolutePath() != null ? new File(newFile.getAbsolutePath()).getName() : null;
    if (names.contains(name)) {
      if (!MZmineCore.isHeadLessMode()) {
        MZmineCore.getDesktop().displayErrorMessage("Cannot add raw data file " + name
            + " because a file with the same name already exists in the project. Please copy "
            + "the file and rename it, if you want to import it twice.");
      }
      logger.warning(
          "Cannot add file with an original name that already exists in project. (filename="
              + newFile.getName() + ")");
      return;
    }

    logger.finest("Adding a new file to the project: " + newFile.getName());

    FxThreadUtil.runOnFxThreadAndWait(() -> {
      rawDataFilesProperty.get().add(newFile);
{'fraction_non_alphanumeric': 0.106411523796074, 'fraction_numerical': 0.0033893517864708373, 'mean_word_length': 3.6420845624385447, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3436338', 'n_tokens_mistral': 3979, 'n_tokens_neox': 3878, 'n_words': 1065}