001/*
002 * Copyright 2020  nils.hoffmann.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.isas.lipidomics.domain;
017
018import de.isas.lipidomics.palinom.exceptions.ParsingException;
019import de.isas.lipidomics.palinom.sumformula.SumFormulaVisitorParser;
020import java.util.EnumMap;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024/**
025 * Accounting table for chemical element frequency. This is used to calculate
026 * sum formulas and total masses for a given chemical element distribution, e.g.
027 * in a lipid.
028 *
029 * @author  nils.hoffmann
030 */
031public final class ElementTable extends EnumMap<Element, Integer> {
032
033    public ElementTable(Map<Element, ? extends Integer> m) {
034        super(m);
035    }
036
037    /**
038     * Creates an empty element table.
039     */
040    public ElementTable() {
041        super(Element.class);
042    }
043
044    /**
045     * Creates the element table from the provided sum formula. If an empty
046     * string is passed in this will create an empty table.
047     *
048     * @param sumFormula the sum formula to parse.
049     * @throws ParsingException if the sum formula does not conform with the
050     * SumFormula grammar.
051     */
052    public ElementTable(String sumFormula) throws ParsingException {
053        this();
054        if (!sumFormula.isEmpty()) {
055            SumFormulaVisitorParser parser = new SumFormulaVisitorParser();
056            add(parser.parse(sumFormula));
057        }
058    }
059
060    /**
061     * Adds the element counts of the provided table to this one.
062     *
063     * @param other the table to add to this one.
064     * @return a new element table.
065     */
066    public ElementTable add(ElementTable other) {
067        other.entrySet().stream().filter((entry) -> {
068            return entry.getValue() != null;
069        }).forEach((entry) -> {
070            incrementBy(entry.getKey(), entry.getValue());
071        });
072        return this;
073    }
074
075    /**
076     * Increment the count of the provided element by one.
077     *
078     * @param element the element.
079     */
080    public void increment(Element element) {
081        merge(element, 1, Integer::sum);
082    }
083
084    /**
085     * Increment the count of the provided element by the given number.
086     *
087     * @param element the element.
088     * @param increment the increment for the element.
089     */
090    public void incrementBy(Element element, Integer increment) {
091        merge(element, increment, Integer::sum);
092    }
093
094    /**
095     * Decrements the current count for element by one.
096     *
097     * @param element the element to decrement the counts for.
098     */
099    public void decrement(Element element) {
100        incrementBy(element, -1);
101    }
102
103    /**
104     * Decrement the count of the provided element by the given number.
105     *
106     * @param element the element.
107     * @param decrement the decrement for the element.
108     */
109    public void decrementBy(Element element, Integer decrement) {
110        merge(element, -decrement, Integer::sum);
111    }
112
113    /**
114     * Negates the count stored in the table. E.g. '5' will become '-5', '-5'
115     * would become '5'.
116     *
117     * @param element the element count to negate.
118     */
119    public void negate(Element element) {
120        Integer count = getOrDefault(element, 0);
121        put(element, -1 * count);
122    }
123
124    /**
125     * Subtracts all element counts in the provided element table from this
126     * table.
127     *
128     * @param elementTable the element table to subtract from this.
129     * @return this element table.
130     */
131    public ElementTable subtract(ElementTable elementTable) {
132        elementTable.entrySet().stream().filter((entry) -> {
133            return entry.getValue() != null;
134        }).forEach((entry) -> {
135            decrementBy(entry.getKey(), entry.getValue());
136        });
137        return this;
138    }
139
140    /**
141     * Returns the sum formula for all elements in this table.
142     *
143     * @return the sum formula. Returns an empty string if the table is empty.
144     */
145    public String getSumFormula() {
146        return entrySet().stream().filter((entry) -> {
147            return entry.getValue() != null;
148        }).map((entry) -> {
149            return entry.getKey().getName() + ((entry.getValue() > 1) ? entry.getValue() : "");
150        }).collect(Collectors.joining());
151    }
152
153    /**
154     * Returns the individual total mass for the provided element.
155     *
156     * @param element the element to calculate the total mass for.
157     * @return the total mass for the given element, or 0.
158     */
159    public Double getMass(Element element) {
160        Integer count = getOrDefault(element, 0);
161        return count.doubleValue() * element.getMass();
162    }
163
164    /**
165     * Returns the total summed mass per number of elements.
166     *
167     * @return the total summed mass for this element table. Returns 0 if the
168     * table is empty.
169     */
170    public Double getMass() {
171        return keySet().stream().map((key) -> {
172            return getMass(key);
173        }).reduce(0.0d, Double::sum);
174    }
175
176    /**
177     * Returns an copy of all mappings in this table.
178     *
179     * @return the element table copy.
180     */
181    public ElementTable copy() {
182        return new ElementTable(this);
183    }
184
185}