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 nDoubleBonds the number of double bonds in this FA. 050 * @param doubleBondPositions double bond positions in this FA. 051 * @see LipidFaBondType 052 */ 053 @Builder(builderMethodName = "isomericFattyAcidBuilder", builderClassName = "IsomericFattyAcidBuilder") 054 public FattyAcid(String name, int position, int nCarbon, int nHydroxy, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications, int nDoubleBonds, Map<Integer, String> doubleBondPositions) { 055 this.name = name; 056 if (nCarbon < 0) { 057 throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!"); 058 } 059 this.position = position; 060 if (position < -1) { 061 throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!"); 062 } 063 this.nCarbon = nCarbon; 064 if (nHydroxy < 0) { 065 throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!"); 066 } 067 this.nHydroxy = nHydroxy; 068 this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED); 069 this.lcb = lcb; 070 this.modifications = modifications == null ? ModificationsList.NONE : modifications; 071 if (doubleBondPositions == null) { 072 this.doubleBondPositions = Collections.emptyMap(); 073 this.nDoubleBonds = nDoubleBonds; 074 } else { 075 if (nDoubleBonds != doubleBondPositions.size()) { 076 throw new ConstraintViolationException("Isomeric FattyAcid must receive double bond positions for all double bonds! Got " + nDoubleBonds + " double bonds and " + doubleBondPositions.size() + " positions: " + doubleBondPositions); 077 } 078 this.doubleBondPositions = new TreeMap<>(); 079 this.doubleBondPositions.putAll(doubleBondPositions); 080 this.nDoubleBonds = this.doubleBondPositions.size(); 081 } 082 this.type = FattyAcidType.ISOMERIC; 083 } 084 085 /** 086 * Create a new structural level FattyAcid. 087 * 088 * @param name the name, e.g. FA1 for the first FA. 089 * @param position the sn position. -1 if undefined or unknown. 090 * @param nCarbon the number of carbons in this FA. 091 * @param nHydroxy the number of hydroxyls on this FA. 092 * @param nDoubleBonds the number of double bonds in this FA. 093 * @param lipidFaBondType the bond type, e.g. ESTER. 094 * @param lcb true if this is a long-chain base, e.g. in a Ceramide. 095 * @param modifications optional modifications for this FA. 096 * @see LipidFaBondType 097 */ 098 @Builder(builderMethodName = "structuralFattyAcidBuilder", builderClassName = "StructuralFattyAcidBuilder") 099 public FattyAcid(String name, int position, int nCarbon, int nHydroxy, int nDoubleBonds, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications) { 100 this.name = name; 101 if (nCarbon < 0) { 102 throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!"); 103 } 104 this.position = position; 105 if (position < -1) { 106 throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!"); 107 } 108 this.nCarbon = nCarbon; 109 if (nHydroxy < 0) { 110 throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!"); 111 } 112 this.nHydroxy = nHydroxy; 113 if (nDoubleBonds < 0) { 114 throw new ConstraintViolationException("FattyAcid must have at least 0 double bonds!"); 115 } 116 this.nDoubleBonds = nDoubleBonds; 117 this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED); 118 this.lcb = lcb; 119 this.modifications = modifications == null ? ModificationsList.NONE : modifications; 120 this.doubleBondPositions = Collections.emptyMap(); 121 this.type = FattyAcidType.STRUCTURAL; 122 } 123 124 /** 125 * Create a new molecular level FattyAcid. 126 * 127 * @param name the name, e.g. FA1 for the first FA. 128 * @param nCarbon the number of carbons in this FA. 129 * @param nHydroxy the number of hydroxyls on this FA. 130 * @param nDoubleBonds the number of double bonds in this FA. 131 * @param lipidFaBondType the bond type, e.g. ESTER. 132 * @param lcb true if this is a long-chain base, e.g. in a Ceramide. 133 * @param modifications optional modifications for this FA. 134 * @see LipidFaBondType 135 */ 136 @Builder(builderMethodName = "molecularFattyAcidBuilder", builderClassName = "MolecularFattyAcidBuilder") 137 public FattyAcid(String name, int nCarbon, int nHydroxy, int nDoubleBonds, LipidFaBondType lipidFaBondType, boolean lcb, ModificationsList modifications) { 138 this.name = name; 139 if (nCarbon < 0) { 140 throw new ConstraintViolationException("FattyAcid must have at least 0 carbons!"); 141 } 142 this.position = -1; 143 if (position < -1) { 144 throw new ConstraintViolationException("FattyAcid position must be greater or equal to -1 (undefined) or greater or equal to 0 (0 = first position)!"); 145 } 146 this.nCarbon = nCarbon; 147 if (nHydroxy < 0) { 148 throw new ConstraintViolationException("FattyAcid must have at least 0 hydroxy groups!"); 149 } 150 this.nHydroxy = nHydroxy; 151 if (nDoubleBonds < 0) { 152 throw new ConstraintViolationException("FattyAcid must have at least 0 double bonds!"); 153 } 154 this.nDoubleBonds = nDoubleBonds; 155 this.lipidFaBondType = Optional.ofNullable(lipidFaBondType).orElse(LipidFaBondType.UNDEFINED); 156 this.lcb = lcb; 157 this.modifications = modifications == null ? ModificationsList.NONE : modifications; 158 this.doubleBondPositions = Collections.emptyMap(); 159 this.type = FattyAcidType.MOLECULAR; 160 } 161 162 /** 163 * Build the name of this substructure. 164 * 165 * @param level the structural lipid level to return this substructure's 166 * name on. 167 * @return the name of this substructure. 168 */ 169 public String buildSubstructureName(LipidLevel level) { 170 StringBuilder sb = new StringBuilder(); 171 int nDB = 0; 172 int nHydroxy = 0; 173 int nCarbon = 0; 174 nDB += getNDoubleBonds(); 175 StringBuilder dbPos = new StringBuilder(); 176 List<String> dbPositions = new LinkedList<>(); 177 for (Integer key : getDoubleBondPositions().keySet()) { 178 dbPositions.add(key + getDoubleBondPositions().get(key)); 179 } 180 if (!getDoubleBondPositions().isEmpty()) { 181 dbPos. 182 append("("). 183 append(dbPositions.stream().collect(Collectors.joining(","))). 184 append(")"); 185 } 186 187 nCarbon += getNCarbon(); 188 nHydroxy += getNHydroxy(); 189 sb. 190 append(nCarbon). 191 append(":"). 192 append(nDB). 193 append(dbPos.toString()). 194 append(nHydroxy > 0 ? ";" + nHydroxy : ""). 195 append(getLipidFaBondType().suffix()); 196 //TODO reenable once LSI has finished modification specification 197// if (!getModifications().isEmpty()) { 198// sb.append("("); 199// sb.append(getModifications().stream().map((t) -> { 200// return (t.getLeft() == -1 ? "" : t.getLeft()) + "" + t.getRight(); 201// }).collect(Collectors.joining(","))); 202// sb.append(")"); 203// } 204 return sb.toString(); 205 } 206 207 public ElementTable getElements() { 208 ElementTable table = new ElementTable(); 209 if (!isLcb()) { 210 if (nCarbon > 0 || nDoubleBonds > 0) { 211 table.incrementBy(Element.ELEMENT_C, nCarbon);// C 212 switch (lipidFaBondType) { 213 case ESTER: 214 table.incrementBy(Element.ELEMENT_H, 2 * nCarbon - 1 - 2 * nDoubleBonds); // H 215 table.incrementBy(Element.ELEMENT_O, 1 + nHydroxy); // O 216 break; 217 case ETHER_PLASMENYL: 218 table.incrementBy(Element.ELEMENT_H, 2 * nCarbon - 1 - 2 * nDoubleBonds + 2); // H 219 table.incrementBy(Element.ELEMENT_O, nHydroxy); // O 220 break; 221 case ETHER_PLASMANYL: 222 table.incrementBy(Element.ELEMENT_H, (nCarbon + 1) * 2 - 1 - 2 * nDoubleBonds); // H 223 table.incrementBy(Element.ELEMENT_O, nHydroxy); // O 224 break; 225 default: 226 throw new ConstraintViolationException("Mass cannot be computed for fatty acyl chain with bond type: " + lipidFaBondType); 227 } 228 } 229 } else { 230 // long chain base 231 table.incrementBy(Element.ELEMENT_C, nCarbon); // C 232 table.incrementBy(Element.ELEMENT_H, 2 * (nCarbon - nDoubleBonds) + 1); // H 233 table.incrementBy(Element.ELEMENT_O, nHydroxy); // O 234 table.incrementBy(Element.ELEMENT_N, 1); // N 235 } 236 return table; 237 } 238 239}