Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,9 @@ public Map<String, String> getDataMap() {
streamModifications().map(IonModification::getMolFormula).collect(Collectors.joining(";")));
return map;
}

@Override
public IonModification withCharge(final int newCharge) {
return new CombinedIonModification(mods, type, mass, newCharge);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
Expand Down Expand Up @@ -150,69 +148,6 @@ public IonType(int molecules, IonType ion) {
this(molecules, ion.adduct, ion.mod);
}

public static IonType parseFromString(String str) {
if (str == null) {
return null;
}

// [ 2 M + NH4+H ] 2 +
// groups:
// 1: [ (opt)
// 2: 2 (opt)
// 3: M (mandatory)
// 4: + (+ or - mandatory)
// 5: NH4+H (mandatory)
// 6: ] (opt)
// 7: 2 (opt)
// 8: + (+ or - opt)

final Pattern pattern = Pattern.compile(
"(\\[)?(\\d*)(M)([\\+\\-])([a-zA-Z_0-9\\\\+\\\\-]+)([\\]])?([\\d])?([\\+\\-])?");
final Matcher matcher = pattern.matcher(str);
if (!matcher.matches()) {
return null;
}

StringBuilder b = new StringBuilder();
for (int i = 0; i < matcher.groupCount(); i++) {
b.append("group ").append(i).append(" ").append(matcher.group(i));
}

final int numMolecules = matcher.group(2) == null || matcher.group(2).isBlank() ? 1
: Integer.parseInt(matcher.group(2));
final int absCharge = matcher.group(7) == null || matcher.group(7).isBlank() ? 1
: Integer.parseInt(matcher.group(7));

final String modification = matcher.group(5);
if (modification == null || modification.isBlank()) {
return null;
}

final PolarityType pol =
matcher.group(8) == null || matcher.group(8).isBlank() ? PolarityType.POSITIVE
: PolarityType.fromSingleChar(matcher.group(8));

IonModification mod = switch (pol) {
case POSITIVE -> Arrays.stream(IonModification.DEFAULT_VALUES_POSITIVE)
.filter(m -> m.getName().equals(modification) || modification.equals(m.getMolFormula()))
.findFirst().orElse(null);
case NEGATIVE -> Arrays.stream(IonModification.DEFAULT_VALUES_NEGATIVE)
.filter(m -> m.getName().equals(modification) || modification.equals(m.getMolFormula()))
.findFirst().orElse(null);
default -> null;
};
if (mod == null) {
return null;
}

final IonType ionType = new IonType(numMolecules, mod);
if (ionType.getAbsCharge() != absCharge) {
return null;
}

return ionType;
}

/**
* All modifications
*
Expand Down Expand Up @@ -336,8 +271,10 @@ public IonType createModified(final @NotNull IonModification... newMod) {

public String toString(boolean showMass) {
int absCharge = Math.abs(charge);
String z = absCharge > 1 ? absCharge + "" : "";
z += (charge < 0 ? "-" : "+");
String z = charge < 0 ? "-" : "+";
if (absCharge > 1) {
z += absCharge;
}
if (charge == 0) {
z = "";
}
Expand Down Expand Up @@ -598,7 +535,8 @@ public IMolecularFormula addToFormula(IMolecularFormula formula)
return result;
}

@Override

@Override
public boolean equals(final Object obj) {
if (obj == null || !obj.getClass().equals(this.getClass())
|| !(obj instanceof final IonType a)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright (c) 2004-2022 The MZmine Development Team
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

package io.github.mzmine.datamodel.identities.iontype;

import io.github.mzmine.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Parses strings to {@link IonType}
*/
public class IonTypeParser {

private static final Logger logger = Logger.getLogger(IonTypeParser.class.getName());

/**
* Pattern that groups +3H2O to + 3 H2O
*/
private static final Pattern PART_PATTERN = Pattern.compile("([+-])(\\d*)(\\w+)");

@Nullable
public static IonType parse(final @Nullable String str) {
if (str == null || str.isBlank()) {
return null;
}
// clean up but keep [] for now
String clean = str.replaceAll("[^a-zA-Z0-9+-\\[\\]]", "");
String[] splitCharge = clean.split("]");
// default charge is 1 - because we are usually looking at charged ions
int charge = 1;
if (splitCharge.length > 1) {
// [M+H]+ to [M+H and +
charge = StringUtils.parseSignAndIntegerOrElse(splitCharge[1], true, 1);
clean = splitCharge[0];
} else {
// read charge that was not separated by ']' so maybe from M+H or M+H+
int lastPlusMinusSignIndex = StringUtils.findLastPlusMinusSignIndex(clean, true);
if (lastPlusMinusSignIndex > -1) {
charge = StringUtils.parseSignAndIntegerOrElse(clean.substring(lastPlusMinusSignIndex-1),
true, 1);
clean = clean.substring(0, lastPlusMinusSignIndex);
}
}

// remove all other characters (already cleaned before)
clean = clean.replaceAll("[\\[\\]]", "");
int starti = 0;

int molMultiplier = 1;
boolean molFound = false;
List<IonModification> mods = new ArrayList<>();

for (int i = 0; i < clean.length(); i++) {
char c = clean.charAt(i);
if (c == '+') {
String mod = clean.substring(starti, i);
if (!molFound) {
// remove the M from the end of 2M or M
molMultiplier = getMolMultiplier(mod, 1);
molFound = true;
} else {
parseAndAddIonModifications(mods, mod);
}
starti = i;
}
if (c == '-') {
String mod = clean.substring(starti, i);
if (!molFound) {
// remove the M from the end of 2M or M
molMultiplier = getMolMultiplier(mod, 1);
molFound = true;
} else {
parseAndAddIonModifications(mods, mod);
}
starti = i;
}
}
//

// charge was already removed - remainder is the last modification part
String remainder = clean.substring(starti);
if (!molFound) {
// remove the M from the end of 2M or M
molMultiplier = getMolMultiplier(remainder, 1);
molFound = true;
} else {
parseAndAddIonModifications(mods, remainder);
}

IonType ion = createIon(molMultiplier, mods);
int chargeDiff = charge - ion.getCharge();
if (chargeDiff != 0) {
var electron = chargeDiff>0? IonModification.M_PLUS : IonModification.M_MINUS;
for(int c=0; c<Math.abs(chargeDiff); c++){
mods.add(electron);
}
return createIon(molMultiplier, mods);
} else {
return ion;
}
}

@NotNull
private static IonType createIon(final int molMultiplier, final List<IonModification> mods) {
var adducts = mods.stream().filter(Objects::nonNull).filter(m -> m.getAbsCharge() > 0)
.toArray(IonModification[]::new);
var modifications = mods.stream().filter(Objects::nonNull).filter(m -> m.getAbsCharge() == 0)
.toArray(IonModification[]::new);

return new IonType(molMultiplier, CombinedIonModification.create(adducts),
CombinedIonModification.create(modifications));
}

private static void parseAndAddIonModifications(final List<IonModification> mods, String mod) {
// mod is +Na or -H so with sign
// handle +2H by removing the number
var matcher = PART_PATTERN.matcher(mod);
if (!matcher.find()) {
return;
}

// keep parsing prefix number like this
int adductMultiplier = StringUtils.parseIntegerPrefixOrElse(mod, 1);

// need +H or -H2O to get the correct part
String sign = StringUtils.orDefault(matcher.group(1), "+");
String part = sign + StringUtils.orDefault(matcher.group(3), "");

var ionPart = IonModification.parseFromString(part);

for (int j = 0; j < Math.abs(adductMultiplier); j++) {
mods.add(ionPart);
}
}

private static int getMolMultiplier(String mod, int defaultValue) {
mod = mod.substring(0, mod.length() - 1);
if (!mod.isBlank()) {
try {
return Integer.parseInt(mod);
} catch (Exception ex) {
logger.finest("Cannot parse prefix of M in ion notation");
}
}
return defaultValue;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import io.github.mzmine.datamodel.features.types.numbers.NeutralMassType;
import io.github.mzmine.datamodel.features.types.numbers.PrecursorMZType;
import io.github.mzmine.datamodel.features.types.numbers.RTType;
import io.github.mzmine.datamodel.identities.iontype.IonType;
import io.github.mzmine.datamodel.identities.iontype.IonTypeParser;
import io.github.mzmine.modules.dataprocessing.id_ion_identity_networking.ionidnetworking.IonNetworkLibrary;
import io.github.mzmine.modules.dataprocessing.id_onlinecompounddb.OnlineDatabases;
import io.github.mzmine.parameters.ParameterSet;
Expand Down Expand Up @@ -446,8 +446,7 @@ private CompoundDBAnnotation getCompoundFromLine(@NotNull String[] values,
doIfNotNull(inchiKey, () -> a.put(inchiKeyType, inchiKey));
doIfNotNull(lineMZ, () -> a.put(precursorMz, lineMZ));
doIfNotNull(neutralMass, () -> a.put(neutralMassType, neutralMass));
doIfNotNull(IonType.parseFromString(lineAdduct),
() -> a.put(ionTypeType, IonType.parseFromString(lineAdduct)));
a.putIfNotNull(ionTypeType, IonTypeParser.parse(lineAdduct));
doIfNotNull(pubchemId, () -> a.put(new DatabaseMatchInfoType(),
new DatabaseMatchInfo(OnlineDatabases.PubChem, pubchemId)));
return a;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/io/github/mzmine/util/CSVParsingUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import io.github.mzmine.datamodel.features.types.numbers.abstr.FloatType;
import io.github.mzmine.datamodel.features.types.numbers.abstr.IntegerType;
import io.github.mzmine.datamodel.identities.iontype.IonType;
import io.github.mzmine.datamodel.identities.iontype.IonTypeParser;
import io.github.mzmine.modules.dataprocessing.id_ion_identity_networking.ionidnetworking.IonNetworkLibrary;
import io.github.mzmine.parameters.parametertypes.ImportType;
import io.github.mzmine.taskcontrol.TaskStatus;
Expand Down Expand Up @@ -122,8 +123,8 @@ public static CompoundDBAnnotation csvLineToCompoundDBAnnotation(final String[]
case IntegerType it -> annotation.put(it, Integer.parseInt(line[columnIndex]));
case DoubleType dt -> annotation.put(dt, Double.parseDouble(line[columnIndex]));
case IonAdductType ignored -> {
final IonType ionType = IonType.parseFromString(line[columnIndex]);
annotation.put(IonTypeType.class, ionType);
final IonType ionType = IonTypeParser.parse(line[columnIndex]);
annotation.putIfNotNull(IonTypeType.class, ionType);
}
case StringType st -> annotation.put(st, line[columnIndex]);
default -> throw new RuntimeException(
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/io/github/mzmine/util/FormulaUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -324,12 +324,22 @@ public static boolean checkMolecularFormula(String formula) {
return true;
}

/**
* @param formula formula maybe with defined isotopes
* @return mono isotopic mass
*/
public static double getMonoisotopicMass(IMolecularFormula formula) {
return formula == null ? 0d
: MolecularFormulaManipulator.getMass(formula, MolecularFormulaManipulator.MonoIsotopic);
}

/**
* Creates a formula with the major isotopes (important to use this method for exact mass
* calculation over the CDK version, which generates formulas without an exact mass)
*
* @return the formula or null
*/
@Nullable
public static IMolecularFormula createMajorIsotopeMolFormula(String formula) {
try {
// new formula consists of isotopes without exact mass
Expand Down
Loading