001/* 002 * 003 */ 004package de.isas.lipidomics.domain; 005 006import de.isas.lipidomics.palinom.exceptions.ConstraintViolationException; 007import java.util.Collections; 008import java.util.LinkedList; 009import java.util.List; 010import java.util.Map; 011import java.util.Optional; 012import java.util.TreeMap; 013import java.util.stream.Collectors; 014import lombok.Builder; 015import lombok.Data; 016 017/** 018 * A fatty acid with a specific type. This object defines the name, position, 019 * number of carbon atoms, hydroxyls and double bonds, as well as the bond type 020 * to the head group. A FattyAcid can carry optional modifications and can 021 * report double bond positions. 022 * 023 * @author nils.hoffmann 024 */ 025@Data 026public class FattyAcid { 027 028 private final FattyAcidType type; 029 private final String name; 030 private final int position; 031 private final int nCarbon; 032 private final int nHydroxy; 033 private final int nDoubleBonds; 034 private final LipidFaBondType lipidFaBondType; 035 private final boolean lcb; 036 private final ModificationsList modifications; 037 private final Map<Integer, String> doubleBondPositions; 038 039 /** 040 * Create a new isomeric level FattyAcid. 041 * 042 * @param name the name, e.g. FA1 for the first FA. 043 * @param position the sn position. -1 if undefined or unknown. 044 * @param nCarbon the number of carbons in this FA. 045 * @param nHydroxy the number of hydroxyls on this FA. 046 * @param lipidFaBondType the bond type, e.g. ESTER. 047 * @param lcb true if this is a long-chain base, e.g. in a Ceramide. 048 * @param modifications optional modifications for this FA. 049 * @param doubleBondPositions double bond positions in this FA. 050 * @see LipidFaBondType 051 */ 052 @Builder(builderMethodName = "isomericFattyAcidBuilder", builderClassName = "IsomericFattyAcidBuilder") 053 public FattyAcid(String name, int position, int nCarbon, int nHydroxy, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications, int nDoubleBonds, Map<Integer, String> doubleBondPositions) { 054 this.name = name; 055 if (nCarbon < 0) { 056 throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!"); 057 } 058 this.position = position; 059 if (position < -1) { 060 throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!"); 061 } 062 this.nCarbon = nCarbon; 063 if (nHydroxy < 0) { 064 throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!"); 065 } 066 this.nHydroxy = nHydroxy; 067 this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED); 068 this.lcb = lcb; 069 this.modifications = modifications == null ? ModificationsList.NONE : modifications; 070 if (doubleBondPositions == null) { 071 this.doubleBondPositions = Collections.emptyMap(); 072 this.nDoubleBonds = nDoubleBonds; 073 } else { 074 if (nDoubleBonds != doubleBondPositions.size()) { 075 throw new ConstraintViolationException("Isomeric FattyAcid must receive double bond positions for all double bonds! Got " + nDoubleBonds + " double bonds and " + doubleBondPositions.size() + " positions: " + doubleBondPositions); 076 } 077 this.doubleBondPositions = new TreeMap<>(); 078 this.doubleBondPositions.putAll(doubleBondPositions); 079 this.nDoubleBonds = this.doubleBondPositions.size(); 080 } 081 this.type = FattyAcidType.ISOMERIC; 082 } 083 084 /** 085 * Create a new structural level FattyAcid. 086 * 087 * @param name the name, e.g. FA1 for the first FA. 088 * @param position the sn position. -1 if undefined or unknown. 089 * @param nCarbon the number of carbons in this FA. 090 * @param nHydroxy the number of hydroxyls on this FA. 091 * @param lipidFaBondType the bond type, e.g. ESTER. 092 * @param lcb true if this is a long-chain base, e.g. in a Ceramide. 093 * @param modifications optional modifications for this FA. 094 * @see LipidFaBondType 095 */ 096 @Builder(builderMethodName = "structuralFattyAcidBuilder", builderClassName = "StructuralFattyAcidBuilder") 097 public FattyAcid(String name, int position, int nCarbon, int nHydroxy, int nDoubleBonds, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications) { 098 this.name = name; 099 if (nCarbon < 0) { 100 throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!"); 101 } 102 this.position = position; 103 if (position < -1) { 104 throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!"); 105 } 106 this.nCarbon = nCarbon; 107 if (nHydroxy < 0) { 108 throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!"); 109 } 110 this.nHydroxy = nHydroxy; 111 if (nDoubleBonds < 0) { 112 throw new ConstraintViolationException("FattyAcid must have at least 0 double bonds!"); 113 } 114 this.nDoubleBonds = nDoubleBonds; 115 this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED); 116 this.lcb = lcb; 117 this.modifications = modifications == null ? ModificationsList.NONE : modifications; 118 this.doubleBondPositions = Collections.emptyMap(); 119 this.type = FattyAcidType.STRUCTURAL; 120 } 121 122 /** 123 * Create a new molecular level FattyAcid. 124 * 125 * @param name the name, e.g. FA1 for the first FA. 126 * @param nCarbon the number of carbons in this FA. 127 * @param nHydroxy the number of hydroxyls on this FA. 128 * @param lipidFaBondType the bond type, e.g. ESTER. 129 * @param lcb true if this is a long-chain base, e.g. in a Ceramide. 130 * @param modifications optional modifications for this FA. 131 * @see LipidFaBondType 132 */ 133 @Builder(builderMethodName = "molecularFattyAcidBuilder", builderClassName = "MolecularFattyAcidBuilder") 134 public FattyAcid(String name, int nCarbon, int nHydroxy, int nDoubleBonds, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications) { 135 this.name = name; 136 if (nCarbon < 0) { 137 throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!"); 138 } 139 this.position = -1; 140 if (position < -1) { 141 throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!"); 142 } 143 this.nCarbon = nCarbon; 144 if (nHydroxy < 0) { 145 throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!"); 146 } 147 this.nHydroxy = nHydroxy; 148 if (nDoubleBonds < 0) { 149 throw new ConstraintViolationException("FattyAcid must have at least 0 double bonds!"); 150 } 151 this.nDoubleBonds = nDoubleBonds; 152 this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED); 153 this.lcb = lcb; 154 this.modifications = modifications == null ? ModificationsList.NONE : modifications; 155 this.doubleBondPositions = Collections.emptyMap(); 156 this.type = FattyAcidType.MOLECULAR; 157 } 158 159 /** 160 * Build the name of this substructure. 161 * 162 * @param level the structural lipid level to return this substructure's 163 * name on. 164 * @return the name of this substructure. 165 */ 166 public String buildSubstructureName(LipidLevel level) { 167 StringBuilder sb = new StringBuilder(); 168 int nDB = 0; 169 int nHydroxy = 0; 170 int nCarbon = 0; 171 nDB += getNDoubleBonds(); 172 StringBuilder dbPos = new StringBuilder(); 173 List<String> dbPositions = new LinkedList<>(); 174 for (Integer key : getDoubleBondPositions().keySet()) { 175 dbPositions.add(key + getDoubleBondPositions().get(key)); 176 } 177 if (!getDoubleBondPositions().isEmpty()) { 178 dbPos. 179 append("("). 180 append(dbPositions.stream().collect(Collectors.joining(","))). 181 append(")"); 182 } 183 184 nCarbon += getNCarbon(); 185 nHydroxy += getNHydroxy(); 186 sb. 187 append(nCarbon). 188 append(":"). 189 append(nDB). 190 append(dbPos.toString()). 191 append(nHydroxy > 0 ? ";" + nHydroxy : ""). 192 append(getLipidFaBondType().suffix()); 193 if (!getModifications().isEmpty()) { 194 sb.append("("); 195 sb.append(getModifications().stream().map((t) -> { 196 return (t.getLeft() == -1 ? "" : t.getLeft()) + "" + t.getRight(); 197 }).collect(Collectors.joining(","))); 198 sb.append(")"); 199 } 200 return sb.toString(); 201 } 202 203 public ElementTable getElements() { 204 ElementTable table = new ElementTable(); 205 if (!isLcb()) { 206 if (nCarbon > 0 || nDoubleBonds > 0) { 207 table.incrementBy(Element.ELEMENT_C, nCarbon);// C 208 switch (lipidFaBondType) { 209 case ESTER: 210 table.incrementBy(Element.ELEMENT_H, 2 * nCarbon - 1 - 2 * nDoubleBonds); // H 211 table.incrementBy(Element.ELEMENT_O, 1 + nHydroxy); // O 212 break; 213 case ETHER_PLASMENYL: 214 table.incrementBy(Element.ELEMENT_H, 2 * nCarbon - 1 - 2 * nDoubleBonds + 2); // H 215 table.incrementBy(Element.ELEMENT_O, nHydroxy); // O 216 break; 217 case ETHER_PLASMANYL: 218 table.incrementBy(Element.ELEMENT_H, (nCarbon + 1) * 2 - 1 - 2 * nDoubleBonds); // H 219 table.incrementBy(Element.ELEMENT_O, nHydroxy); // O 220 break; 221 default: 222 throw new ConstraintViolationException("Mass cannot be computed for fatty acyl chain with bond type: " + lipidFaBondType); 223 } 224 } 225 } else { 226 // long chain base 227 table.incrementBy(Element.ELEMENT_C, nCarbon); // C 228 table.incrementBy(Element.ELEMENT_H, 2 * (nCarbon - nDoubleBonds) + 1); // H 229 table.incrementBy(Element.ELEMENT_O, nHydroxy); // O 230 table.incrementBy(Element.ELEMENT_N, 1); // N 231 } 232 return table; 233 } 234 235}