001/* 002 * 003 */ 004package de.isas.lipidomics.domain; 005 006import static de.isas.lipidomics.domain.Element.ELEMENT_H; 007import de.isas.lipidomics.palinom.exceptions.ConstraintViolationException; 008import java.util.Collections; 009import java.util.Map; 010import java.util.Optional; 011import lombok.AccessLevel; 012import lombok.Data; 013import lombok.Setter; 014 015/** 016 * A lipid species is the factual root of the object hierarchy. Lipid category 017 * and class are used as taxonomic roots of a lipid species. Partial structural 018 * knowledge, apart from the head group, is first encoded in the lipid species. 019 * 020 * A typical lipid species is PC 32:0 (SwissLipids SLM:000056493), where the 021 * head group is defined as PC (Glycerophosphocholines), with fatty acyl chains 022 * of unknown individual composition, but known total composition (32 carbon 023 * atoms, zero double bonds, no hydroxylations). 024 * 025 * @author nils.hoffmann 026 * @see LipidCategory 027 * @see LipidClass 028 * @see LipidMolecularSubspecies 029 * @see LipidStructuralSubspecies 030 * @see LipidIsomericSubspecies 031 */ 032@Data 033public class LipidSpecies { 034 035 private static final class None extends LipidSpecies { 036 037 private None() { 038 super(new HeadGroup(""), Optional.of(LipidSpeciesInfo.NONE)); 039 } 040 } 041 042 public static final LipidSpecies NONE = new None(); 043 private final HeadGroup headGroup; 044 @Setter(AccessLevel.NONE) 045 protected LipidSpeciesInfo info; 046 047 /** 048 * Create a lipid species using the provided head group and a lipid species 049 * info {@link LipidSpeciesInfo#NONE}. 050 * 051 * @param headGroup the lipid species head group. 052 */ 053 public LipidSpecies(HeadGroup headGroup) { 054 this(headGroup, Optional.of(LipidSpeciesInfo.NONE)); 055 } 056 057 /** 058 * Create a lipid species from a head group and an optional 059 * {@link LipidSpeciesInfo}. This constructor will infer the lipid class 060 * from the head group automatically. It then uses the lipid class to 061 * retrieve the category of this lipid automatically, or sets the category 062 * to {@link LipidCategory#UNDEFINED}. The lipid species info, which 063 * contains details about the total no. of carbons in FA chains, no. of 064 * double bonds etc., is used as provided. 065 * 066 * @param headGroup the lipid species head group. 067 * @param lipidSpeciesInfo the lipid species info object. 068 */ 069 public LipidSpecies(HeadGroup headGroup, Optional<LipidSpeciesInfo> lipidSpeciesInfo) { 070 this.headGroup = headGroup; 071 this.info = lipidSpeciesInfo.orElse(LipidSpeciesInfo.NONE); 072 } 073 074 /** 075 * Returns the {@link LipidSpeciesInfo} for this lipid. 076 * 077 * @return the lipid species info. 078 */ 079 public LipidSpeciesInfo getInfo() { 080 return this.info; 081 } 082 083 /** 084 * Returns true, if the head group ends with ' O' or if the lipid fa bond 085 * type is either {@link LipidFaBondType#ETHER_UNSPECIFIED}, 086 * {@link LipidFaBondType#ETHER_PLASMANYL} or 087 * {@link LipidFaBondType#ETHER_PLASMENYL}. 088 * 089 * @return whether this is an 'ether' lipid, e.g. a unspecified ether 090 * species, a Plasmanyl or Plasmenyl species. 091 */ 092 public boolean isEtherLipid() { 093 LipidSpeciesInfo info = this.info; 094 LipidFaBondType bondType = info.getLipidFaBondType(); 095 return bondType == LipidFaBondType.ETHER_PLASMANYL 096 || bondType == LipidFaBondType.ETHER_PLASMENYL 097 || bondType == LipidFaBondType.ETHER_UNSPECIFIED 098 || getFa().values().stream().anyMatch((t) -> { 099 return t.getLipidFaBondType() == LipidFaBondType.ETHER_UNSPECIFIED 100 || t.getLipidFaBondType() == LipidFaBondType.ETHER_PLASMANYL 101 || t.getLipidFaBondType() == LipidFaBondType.ETHER_PLASMENYL; 102 }); 103 } 104 105 /** 106 * Returns a lipid string representation for the {@link LipidLevel}, e.g. 107 * Category, Species, etc, as returned by {@link #getInfo()}. 108 * 109 * Will return the head group name if the level is 110 * {@link LipidSpeciesInfo#NONE}. 111 * 112 * @return the lipid name for the native level. 113 */ 114 public String getLipidString() { 115 return getLipidString(getInfo().getLevel()); 116 } 117 118 /** 119 * Returns a lipid string representation for the given {@link LipidLevel}, 120 * e.g. Category, Species, etc. Please note that this method is overridden 121 * by specific implementations for molecular, structural and isomeric 122 * subspecies levels. This method does not normalize the head group. 123 * 124 * @param level the lipid level to report the name of this lipid on. 125 * @return the lipid name. 126 */ 127 public String getLipidString(LipidLevel level) { 128 return this.buildLipidString(level, headGroup.getName(), false); 129 } 130 131 /** 132 * Returns a lipid string representation for the given {@link LipidLevel}, 133 * e.g. Category, Species, etc. Please note that this method is overridden 134 * by specific implementations for molecular, structural and isomeric 135 * subspecies levels. This method normalizes the head group to the primary 136 * class-specific synonym. E.g. TG would be normalized to TAG. 137 * 138 * @param level the lipid level to report the name of this lipid on. 139 * @param normalizeHeadGroup if true, use class specific synonym for 140 * headGroup, if false, use head group as parsed. 141 * @return the lipid name. 142 */ 143 public String getLipidString(LipidLevel level, boolean normalizeHeadGroup) { 144 return this.buildLipidString(level, normalizeHeadGroup ? getNormalizedHeadGroup() : headGroup.getName(), normalizeHeadGroup); 145 } 146 147 protected StringBuilder buildSpeciesHeadGroupString(String headGroup, boolean normalizeHeadGroup) { 148 StringBuilder lipidString = new StringBuilder(); 149 lipidString.append(Optional.ofNullable(this.headGroup.getLipidClass()).map((lclass) -> { 150 switch (lclass) { 151// case SE: 152 case SE_27_1: 153 case SE_27_2: 154 case SE_28_2: 155 case SE_28_3: 156 case SE_29_2: 157 case SE_30_2: 158 return getNormalizedHeadGroup() + "/"; // use this for disambiguation to avoid SE 16:1 to be similar to SE 43:2 because of expansion to SE 27:1/16:1 159 } 160 return headGroup + " "; 161 }).orElse(headGroup + " ")); 162 return lipidString; 163 } 164 165 protected String buildLipidString(LipidLevel level, String headGroup, boolean isNormalized) throws ConstraintViolationException { 166 switch (level) { 167 case CATEGORY: 168 return this.headGroup.getLipidCategory().name(); 169 case CLASS: 170 return this.headGroup.getLipidClass().name(); 171 case SPECIES: 172 StringBuilder lipidString = new StringBuilder(); 173 lipidString.append(buildSpeciesHeadGroupString(headGroup, isNormalized)); 174 LipidSpeciesInfo info = this.info; 175 if (info.getNCarbon() > 0) { 176 int nCarbon = info.getNCarbon(); 177 String hgToFaSep = ""; 178 if (isEtherLipid()) { 179 hgToFaSep = "O-"; 180 } 181 lipidString.append(hgToFaSep).append(nCarbon); 182 int nDB = info.getNDoubleBonds(); 183 lipidString.append(":").append(nDB); 184 int nHydroxy = info.getNHydroxy(); 185 lipidString.append(nHydroxy > 0 ? ";" + nHydroxy : ""); 186 lipidString.append(info.getLipidFaBondType().suffix()); 187 //TODO reenable once LSI has finished modification specification 188// if (!info.getModifications().isEmpty()) { 189// lipidString.append("("); 190// lipidString.append(info.getModifications().stream().map((t) -> { 191// return (t.getLeft() == -1 ? "" : t.getLeft()) + "" + t.getRight(); 192// }).collect(Collectors.joining(","))); 193// lipidString.append(")"); 194// } 195 } 196 return lipidString.toString().trim(); 197 case UNDEFINED: 198 return this.headGroup.getName(); 199 default: 200 LipidLevel thisLevel = getInfo().getLevel(); 201 throw new ConstraintViolationException(getClass().getSimpleName() + " can not create a string for lipid with level " + thisLevel + " for level " + level + ": target level is more specific than this lipid's level!"); 202 } 203 } 204 205 /** 206 * Returns a lipid string representation for the head group of this lipid. 207 * This method normalizes the original head group name to the class specific 208 * primary alias, if the level and class are known. E.g. TG is normalized to 209 * TAG. 210 * 211 * @return the normalized lipid head group. 212 */ 213 public String getNormalizedHeadGroup() { 214 return headGroup.getNormalizedName(); 215 } 216 217 /** 218 * Returns a lipid string representation for the native {@link LipidLevel}, 219 * e.g. Category, Species, etc, as returned by {@link #getInfo()} of this 220 * lipid. This method normalizes the head group to the primary 221 * class-specific synonym. E.g. TG would be normalized to TAG. 222 * 223 * @return the normalized lipid name. 224 */ 225 public String getNormalizedLipidString() { 226 return getLipidString(getInfo().getLevel(), true); 227 } 228 229 /** 230 * Validate this lipid against the class-specific available FA types and 231 * slots. 232 * 233 * @return true if this lipid's FA types and their number match the class 234 * definition, false otherwise. 235 */ 236 public boolean validate() { 237 return true; 238 } 239 240 /** 241 * Returns the fatty acyls registered for this lipid. 242 * 243 * @return the fatty acyls. 244 */ 245 public Map<String, FattyAcid> getFa() { 246 return Collections.emptyMap(); 247 } 248 249 /** 250 * Returns the element count table for this lipid. 251 * 252 * @return the element count table. 253 */ 254 public ElementTable getElements() { 255 ElementTable elements = new ElementTable(); 256 switch (info.getLevel()) { 257 case CATEGORY: 258 case CLASS: 259 case UNDEFINED: 260 return elements; 261 } 262 263 Optional.ofNullable(headGroup.getLipidClass()).ifPresent((lclass) -> { 264 elements.add(lclass.getElements()); 265 }); 266 267 switch (info.getLevel()) { 268 case MOLECULAR_SUBSPECIES: 269 case STRUCTURAL_SUBSPECIES: 270 case ISOMERIC_SUBSPECIES: 271 int nTrueFa = 0; 272 for (FattyAcid fa : getFa().values()) { 273 ElementTable faElements = fa.getElements(); 274 if (fa.getNCarbon() != 0 || fa.getNDoubleBonds() != 0) { 275 nTrueFa += 1; 276 } 277 elements.add(faElements); 278 } 279 if (headGroup.getLipidClass().getMaxNumFa() < nTrueFa) { 280 throw new ConstraintViolationException("Inconsistency in number of fatty acyl chains for lipid '" + headGroup.getName() + "'. Expected at most: " + headGroup.getLipidClass().getMaxNumFa() + "; received: " + nTrueFa); 281 } 282 elements.incrementBy(Element.ELEMENT_H, headGroup.getLipidClass().getMaxNumFa() - nTrueFa); // adding hydrogens for absent fatty acyl chains 283 break; 284 case SPECIES: 285 int maxNumFa = 0; 286 LipidClass lclass = headGroup.getLipidClass(); 287 maxNumFa = lclass.getMaxNumFa(); 288 289 int maxPossNumFa = headGroup.getLipidClass().getAllowedNumFa().stream().max(Integer::compareTo).orElse(0); 290 ElementTable faElements = info.getElements(maxPossNumFa); 291 elements.add(faElements); 292 elements.incrementBy(ELEMENT_H, maxNumFa - maxPossNumFa); // adding hydrogens for absent fatty acyl chains 293 break; 294 default: 295 break; 296 } 297 298 return elements; 299 } 300 301 public LipidClass getLipidClass() { 302 return headGroup.getLipidClass(); 303 } 304 305 public LipidCategory getLipidCategory() { 306 return headGroup.getLipidCategory(); 307 } 308 309 @Override 310 public String toString() { 311 return getLipidString(info.getLevel()); 312 } 313 314}