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}