FattyAcidParserEventHandler.java

/*
 * Copyright 2021 Dominik Kopczynski, Nils Hoffmann.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.lifstools.jgoslin.parser;

import java.util.ArrayDeque;
import org.lifstools.jgoslin.domain.LipidMolecularSpecies;
import org.lifstools.jgoslin.domain.GenericList;
import org.lifstools.jgoslin.domain.LipidFaBondType;
import org.lifstools.jgoslin.domain.LipidStructureDefined;
import org.lifstools.jgoslin.domain.LipidException;
import org.lifstools.jgoslin.domain.KnownFunctionalGroups;
import org.lifstools.jgoslin.domain.Element;
import org.lifstools.jgoslin.domain.LipidSnPosition;
import org.lifstools.jgoslin.domain.Headgroup;
import org.lifstools.jgoslin.domain.FunctionalGroup;
import org.lifstools.jgoslin.domain.LipidCompleteStructure;
import org.lifstools.jgoslin.domain.LipidAdduct;
import org.lifstools.jgoslin.domain.AcylAlkylGroup;
import org.lifstools.jgoslin.domain.DoubleBonds;
import org.lifstools.jgoslin.domain.LipidLevel;
import org.lifstools.jgoslin.domain.Cycle;
import org.lifstools.jgoslin.domain.CarbonChain;
import org.lifstools.jgoslin.domain.FattyAcid;
import org.lifstools.jgoslin.domain.LipidSpecies;
import org.lifstools.jgoslin.domain.LipidFullStructure;
import org.lifstools.jgoslin.domain.Dictionary;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import static java.util.Map.entry;
import java.util.Set;
import org.lifstools.jgoslin.domain.ConstraintViolationException;

/**
 * Event handler implementation for the {@link FattyAcidParser}.
 *
 * @author Dominik Kopczynski
 * @author Nils Hoffmann
 */
public class FattyAcidParserEventHandler extends BaseParserEventHandler<LipidAdduct> {

    private final KnownFunctionalGroups knownFunctionalGroups;
    private LipidLevel level;
    private String headgroup;
    private ArrayDeque<FattyAcid> fattyAcylStack;
    private Dictionary tmp;

    private static final Map<String, Integer> LAST_NUMBERS = Map.ofEntries(
            entry("un", 1),
            entry("hen", 1),
            entry("do", 2),
            entry("di", 2),
            entry("tri", 3),
            entry("buta", 4),
            entry("but", 4),
            entry("tetra", 4),
            entry("penta", 5),
            entry("pent", 5),
            entry("hexa", 6),
            entry("hex", 6),
            entry("hepta", 7),
            entry("hept", 7),
            entry("octa", 8),
            entry("oct", 8),
            entry("nona", 9),
            entry("non", 9)
    );

    private static final Map<String, Integer> SECOND_NUMBERS = Map.ofEntries(
            entry("deca", 10),
            entry("dec", 10),
            entry("eicosa", 20),
            entry("eicos", 20),
            entry("cosa", 20),
            entry("cos", 20),
            entry("triaconta", 30),
            entry("triacont", 30),
            entry("tetraconta", 40),
            entry("tetracont", 40),
            entry("pentaconta", 50),
            entry("pentacont", 50),
            entry("hexaconta", 60),
            entry("hexacont", 60),
            entry("heptaconta", 70),
            entry("heptacont", 70),
            entry("octaconta", 80),
            entry("octacont", 80),
            entry("nonaconta", 90),
            entry("nonacont", 90)
    );

    private static final Map<String, String> FUNC_GROUPS = Map.ofEntries(
            entry("keto", "oxo"),
            entry("ethyl", "Et"),
            entry("hydroxy", "OH"),
            entry("phospho", "Ph"),
            entry("oxo", "oxo"),
            entry("bromo", "Br"),
            entry("methyl", "Me"),
            entry("hydroperoxy", "OOH"),
            entry("homo", ""),
            entry("Epoxy", "Ep"),
            entry("fluro", "F"),
            entry("fluoro", "F"),
            entry("chloro", "Cl"),
            entry("methylene", "My"),
            entry("sulfooxy", "Su"),
            entry("amino", "NH2"),
            entry("sulfanyl", "SH"),
            entry("methoxy", "OMe"),
            entry("iodo", "I"),
            entry("cyano", "CN"),
            entry("nitro", "NO2"),
            entry("OH", "OH"),
            entry("thio", "SH"),
            entry("mercapto", "SH"),
            entry("carboxy", "COOH"),
            entry("acetoxy", "Ac"),
            entry("cysteinyl", "Cys"),
            entry("phenyl", "Phe"),
            entry("s-glutathionyl", "SGlu"),
            entry("s-cysteinyl", "SCys"),
            entry("butylperoxy", "BOO"),
            entry("dimethylarsinoyl", "MMAs"),
            entry("methylsulfanyl", "SMe"),
            entry("imino", "NH"),
            entry("s-cysteinylglycinyl", "SCG")
    );

    private static final Map<String, Integer> ATE = Map.ofEntries(
            entry("formate", 1),
            entry("acetate", 2),
            entry("butyrate", 4),
            entry("propionate", 3),
            entry("valerate", 5),
            entry("isobutyrate", 4)
    );

    private static final Map<String, Integer> SPECIAL_NUMBERS = Map.ofEntries(
            entry("meth", 1),
            entry("etha", 2),
            entry("eth", 2),
            entry("propa", 3),
            entry("isoprop", 3),
            entry("prop", 3),
            entry("propi", 3),
            entry("propio", 3),
            entry("buta", 4),
            entry("but", 4),
            entry("butr", 4),
            entry("furan", 5),
            entry("valer", 5),
            entry("eicosa", 20),
            entry("eicos", 20),
            entry("icosa", 20),
            entry("icos", 20),
            entry("prosta", 20),
            entry("prost", 20),
            entry("prostan", 20)
    );

    private static final Set<String> NOIC_SET = Set.of("noic acid", "nic acid", "dioic_acid");
    private static final Set<String> NAL_SET = Set.of("nal", "dial");
    private static final Set<String> ACETATE_SET = Set.of("acetate", "noate", "nate");

    /**
     * Create a new {@code FattyAcidParserEventHandler}.
     *
     * @param knownFunctionalGroups the known functional groups
     */
    public FattyAcidParserEventHandler(KnownFunctionalGroups knownFunctionalGroups) {
        this.knownFunctionalGroups = knownFunctionalGroups;
        try {
            registeredEvents = Map.ofEntries(
                    entry("lipid_pre_event", this::resetParser),
                    entry("lipid_post_event", this::build_lipid),
                    entry("fatty_acid_post_event", this::set_fatty_acid),
                    entry("fatty_acid_recursion_post_event", this::set_fatty_acid),
                    entry("acid_single_type_pre_event", this::set_fatty_acyl_type),
                    entry("ol_ending_pre_event", this::set_fatty_acyl_type),
                    entry("double_bond_position_pre_event", this::set_double_bond_information),
                    entry("double_bond_position_post_event", this::add_double_bond_information),
                    entry("db_number_post_event", this::set_double_bond_position),
                    entry("cistrans_post_event", this::set_cistrans),
                    entry("acid_type_double_post_event", this::check_db),
                    entry("db_length_pre_event", this::open_db_length),
                    entry("db_length_post_event", this::close_db_length),
                    // lengths
                    entry("functional_length_pre_event", this::reset_length),
                    entry("fatty_length_pre_event", this::reset_length),
                    entry("functional_length_post_event", this::set_functional_length),
                    entry("fatty_length_post_event", this::set_fatty_length),
                    // numbers
                    entry("notation_specials_pre_event", this::special_number),
                    entry("notation_last_digit_pre_event", this::last_number),
                    entry("notation_second_digit_pre_event", this::second_number),
                    // functional groups
                    entry("functional_group_pre_event", this::set_functional_group),
                    entry("functional_group_post_event", this::add_functional_group),
                    entry("functional_pos_pre_event", this::set_functional_pos),
                    entry("functional_position_pre_event", this::set_functional_position),
                    entry("functional_group_type_pre_event", this::set_functional_type),
                    // cyclo / epoxy
                    entry("cyclo_position_pre_event", this::set_functional_group),
                    entry("cyclo_position_post_event", this::rearrange_cycle),
                    entry("epoxy_pre_event", this::set_functional_group),
                    entry("epoxy_post_event", this::add_epoxy),
                    entry("cycle_pre_event", this::set_cycle),
                    entry("methylene_post_event", this::set_methylene),
                    // dioic
                    entry("dioic_pre_event", this::set_functional_group),
                    entry("dioic_post_event", this::set_dioic),
                    entry("dioic_acid_pre_event", this::set_fatty_acyl_type),
                    entry("dial_post_event", this::set_dial),
                    // prosta
                    entry("prosta_pre_event", this::set_prosta),
                    entry("prosta_post_event", this::add_cyclo),
                    entry("reduction_pre_event", this::set_functional_group),
                    entry("reduction_post_event", this::reduction),
                    entry("homo_post_event", this::homo),
                    // recursion
                    entry("recursion_description_pre_event", this::set_recursion),
                    entry("recursion_description_post_event", this::add_recursion),
                    entry("recursion_pos_pre_event", this::set_recursion_pos),
                    entry("yl_ending_pre_event", this::set_yl_ending),
                    entry("acetic_acid_post_event", this::set_acetic_acid),
                    entry("acetic_recursion_pre_event", this::set_recursion),
                    entry("acetic_recursion_post_event", this::add_recursion),
                    entry("hydroxyl_number_pre_event", this::add_hydroxyl),
                    entry("ol_pre_event", this::setup_hydroxyl),
                    entry("ol_post_event", this::add_hydroxyls),
                    entry("ol_pos_post_event", this::set_yl_ending),
                    // wax esters
                    entry("wax_ester_pre_event", this::set_recursion),
                    entry("wax_ester_post_event", this::add_wax_ester),
                    entry("ate_post_event", this::set_ate),
                    entry("isoprop_post_event", this::set_iso),
                    entry("isobut_post_event", this::set_iso),
                    // CoA
                    entry("coa_post_event", this::set_coa),
                    entry("methyl_pre_event", this::set_methyl),
                    // CAR
                    entry("car_pre_event", this::set_car),
                    entry("car_post_event", this::add_car),
                    // furan
                    entry("tetrahydrofuran_pre_event", this::set_tetrahydrofuran),
                    entry("furan_pre_event", this::set_furan),
                    // amine
                    entry("ethanolamine_post_event", this::add_ethanolamine),
                    entry("amine_n_pre_event", this::set_recursion),
                    entry("amine_n_post_event", this::add_amine),
                    entry("amine_post_event", this::add_amine_name),
                    // functional group position summary
                    entry("fg_pos_summary_pre_event", this::set_functional_group),
                    entry("fg_pos_summary_post_event", this::add_summary),
                    entry("func_stereo_pre_event", this::add_func_stereo)
            );
        } catch (Exception e) {
            throw new ConstraintViolationException("Cannot initialize FattyAcidParserEventHandler.", e);
        }
    }

    private String FA_I() {
        return "fa" + Integer.toString(fattyAcylStack.size());
    }

    private void set_lipid_level(LipidLevel _level) {
        level = level.level < _level.level ? level : _level;
    }

    @Override
    protected void resetParser(TreeNode node) {
        content = null;
        level = LipidLevel.FULL_STRUCTURE;
        headgroup = "";
        fattyAcylStack = new ArrayDeque<>();
        fattyAcylStack.add(new FattyAcid("FA", knownFunctionalGroups));
        tmp = new Dictionary();
        tmp.put("fa1", new Dictionary());
    }

    void build_lipid(TreeNode node) {
        if (tmp.containsKey("cyclo_yl")) {
            tmp.put("fg_pos", new GenericList());

            GenericList l1 = new GenericList();
            l1.add(1);
            l1.add("");
            ((GenericList) tmp.get("fg_pos")).add(l1);

            GenericList l2 = new GenericList();
            l2.add((int) tmp.get("cyclo_len"));
            l2.add("");
            ((GenericList) tmp.get("fg_pos")).add(l2);

            add_cyclo(node);
            tmp.remove("cyclo_yl");
            tmp.remove("cyclo_len");
        }

        if (tmp.containsKey("post_adding")) {
            FattyAcid curr_fa_p = fattyAcylStack.peekLast();
            int s = ((GenericList) tmp.get("post_adding")).size();
            curr_fa_p.setNumCarbon(curr_fa_p.getNumCarbon() + s);
            for (int i = 0; i < s; ++i) {
                int pos = (int) ((GenericList) tmp.get("post_adding")).get(i);
                curr_fa_p.addPosition(pos);
                DoubleBonds db = new DoubleBonds(curr_fa_p.getDoubleBonds().getNumDoubleBonds());
                for (Entry<Integer, String> kv : curr_fa_p.getDoubleBonds().getDoubleBondPositions().entrySet()) {
                    db.getDoubleBondPositions().put(kv.getKey() + (kv.getKey() >= pos ? 1 : 0), kv.getValue());
                }
                db.setNumDoubleBonds(db.getDoubleBondPositions().size());
                curr_fa_p.setDoubleBonds(db);
            }
        }

        FattyAcid curr_fa = fattyAcylStack.peekLast();
        if (curr_fa.getDoubleBonds().getDoubleBondPositions().size() > 0) {
            int db_right = 0;
            for (Entry<Integer, String> kv : curr_fa.getDoubleBonds().getDoubleBondPositions().entrySet()) {
                db_right += kv.getValue().length() > 0 ? 1 : 0;
            }
            if (db_right != curr_fa.getDoubleBonds().getDoubleBondPositions().size()) {
                set_lipid_level(LipidLevel.STRUCTURE_DEFINED);
            }
        }

        Headgroup head_group = new Headgroup(headgroup);

        LipidSpecies lipidSpecies = null;

        switch (level) {
            case COMPLETE_STRUCTURE ->
                lipidSpecies = new LipidCompleteStructure(head_group, fattyAcylStack, knownFunctionalGroups);
            case FULL_STRUCTURE ->
                lipidSpecies = new LipidFullStructure(head_group, fattyAcylStack, knownFunctionalGroups);
            case STRUCTURE_DEFINED ->
                lipidSpecies = new LipidStructureDefined(head_group, fattyAcylStack, knownFunctionalGroups);
            case SN_POSITION ->
                lipidSpecies = new LipidSnPosition(head_group, fattyAcylStack, knownFunctionalGroups);
            case MOLECULAR_SPECIES ->
                lipidSpecies = new LipidMolecularSpecies(head_group, fattyAcylStack, knownFunctionalGroups);
            case SPECIES ->
                lipidSpecies = new LipidSpecies(head_group, fattyAcylStack, knownFunctionalGroups);
            default -> {
            }
        }
        if (lipidSpecies!=null) {
            content = new LipidAdduct(lipidSpecies, null);
        }
    }

    void set_fatty_acyl_type(TreeNode node) {
        String t = node.getText();

        if (t.endsWith("ol")) {
            headgroup = "FOH";
        } else if (NOIC_SET.contains(t)) {
            headgroup = "FA";
        } else if (NAL_SET.contains(t)) {
            headgroup = "FAL";
        } else if (ACETATE_SET.contains(t)) {
            headgroup = "WE";
        } else if (t.equals("ne")) {
            headgroup = "HC";
            fattyAcylStack.peekLast().setLipidFaBondType(LipidFaBondType.ETHER);
        } else {
            headgroup = t;
        }
    }

    private void add_amine(TreeNode node) {
        FattyAcid fa = fattyAcylStack.pollLast();

        fa.setLipidFaBondType(LipidFaBondType.AMIDE);
        fattyAcylStack.getLast().setLipidFaBondType(LipidFaBondType.AMIDE);
        fattyAcylStack.addFirst(fa);
    }

    private void set_fatty_acid(TreeNode node) {
        FattyAcid curr_fa = fattyAcylStack.peekLast();
        if (tmp.containsKey("length_pattern")) {

            String length_pattern = (String) tmp.get("length_pattern");
            int[] num = new int[((GenericList) tmp.get("length_tokens")).size()];
            for (int i = 0; i < ((GenericList) tmp.get("length_tokens")).size(); ++i) {
                num[i] = (int) ((GenericList) tmp.get("length_tokens")).get(i);
            }

            int l = 0, d = 0;
            if (length_pattern.equals("L") || length_pattern.equals("S")) {
                l += num[0];
            } else if (length_pattern.equals("LS")) {
                l += num[0] + num[1];
            } else if (length_pattern.equals("LL") || length_pattern.equals("SL") || length_pattern.equals("SS")) {
                l += num[0];
                d += num[1];
            } else if (length_pattern.equals("LSL") || length_pattern.equals("LSS")) {
                l += num[0] + num[1];
                d += num[2];
            } else if (length_pattern.equals("LSLS")) {
                l += num[0] + num[1];
                d += num[2] + num[3];
            } else if (length_pattern.equals("SLS")) {
                l += num[0];
                d += num[1] + num[2];
            } else if (length_pattern.length() > 0 && length_pattern.charAt(0) == 'X') {
                l += num[0];
                for (int i = 1; i < ((GenericList) tmp.get("length_tokens")).size(); ++i) {
                    d += num[i];
                }
            } else if (length_pattern.equals("LLS")) { // false
                throw new ConstraintViolationException("Cannot determine fatty acid and double bond length in '" + node.getText() + "'");
            }
            curr_fa.setNumCarbon(curr_fa.getNumCarbon() + l);
            if (curr_fa.getDoubleBonds().getDoubleBondPositions().isEmpty() && d > 0) {
                curr_fa.getDoubleBonds().setNumDoubleBonds(d);
            }
        }

        if (curr_fa.getFunctionalGroupsInternal().containsKey("noyloxy")) {
            if (headgroup.equals("FA")) {
                headgroup = "FAHFA";
            }

            while (curr_fa.getFunctionalGroupsInternal().get("noyloxy").size() > 0) {
                FattyAcid fa = (FattyAcid) curr_fa.getFunctionalGroupsInternal().get("noyloxy").get(curr_fa.getFunctionalGroupsInternal().get("noyloxy").size() - 1);
                curr_fa.getFunctionalGroupsInternal().get("noyloxy").remove(curr_fa.getFunctionalGroupsInternal().get("noyloxy").size() - 1);

                AcylAlkylGroup acyl = new AcylAlkylGroup(fa, knownFunctionalGroups);
                acyl.setPosition(fa.getPosition());

                if (!curr_fa.getFunctionalGroupsInternal().containsKey("acyl")) {
                    curr_fa.getFunctionalGroupsInternal().put("acyl", new ArrayList<>());
                }
                curr_fa.getFunctionalGroupsInternal().get("acyl").add(acyl);
            }
            curr_fa.getFunctionalGroupsInternal().remove("noyloxy");
        } else if (curr_fa.getFunctionalGroupsInternal().containsKey("nyloxy") || curr_fa.getFunctionalGroupsInternal().containsKey("yloxy")) {
            String yloxy = curr_fa.getFunctionalGroupsInternal().containsKey("nyloxy") ? "nyloxy" : "yloxy";
            while (curr_fa.getFunctionalGroupsInternal().get(yloxy).size() > 0) {
                FattyAcid fa = (FattyAcid) curr_fa.getFunctionalGroupsInternal().get(yloxy).get(curr_fa.getFunctionalGroupsInternal().get(yloxy).size() - 1);
                curr_fa.getFunctionalGroupsInternal().get(yloxy).remove(curr_fa.getFunctionalGroupsInternal().get(yloxy).size() - 1);

                AcylAlkylGroup alkyl = new AcylAlkylGroup(fa, -1, 1, true, knownFunctionalGroups);
                alkyl.setPosition(fa.getPosition());

                if (!curr_fa.getFunctionalGroupsInternal().containsKey("alkyl")) {
                    curr_fa.getFunctionalGroupsInternal().put("alkyl", new ArrayList<>());
                }
                curr_fa.getFunctionalGroupsInternal().get("alkyl").add(alkyl);
            }
            curr_fa.getFunctionalGroupsInternal().remove(yloxy);
        } else {
            boolean has_yl = false;
            for (Entry<String, ArrayList<FunctionalGroup>> kv : curr_fa.getFunctionalGroupsInternal().entrySet()) {
                if (kv.getKey().endsWith("yl")) {
                    has_yl = true;
                    break;
                }
            }
            if (has_yl) {
                while (true) {
                    String yl = "";
                    for (Entry<String, ArrayList<FunctionalGroup>> kv : curr_fa.getFunctionalGroupsInternal().entrySet()) {
                        if (kv.getKey().endsWith("yl")) {
                            yl = kv.getKey();
                            break;
                        }
                    }
                    if (yl.length() == 0) {
                        break;
                    }

                    while (curr_fa.getFunctionalGroupsInternal().get(yl).size() > 0) {
                        FattyAcid fa = (FattyAcid) curr_fa.getFunctionalGroupsInternal().get(yl).get(curr_fa.getFunctionalGroupsInternal().get(yl).size() - 1);
                        curr_fa.getFunctionalGroupsInternal().get(yl).remove(curr_fa.getFunctionalGroupsInternal().get(yl).size() - 1);

                        if (tmp.containsKey("cyclo")) {
                            int cyclo_len = curr_fa.getNumCarbon();
                            tmp.put("cyclo_len", cyclo_len);
                            if (fa.getPosition() != cyclo_len && !tmp.containsKey("furan")) {
                                switch_position(curr_fa, 2 + cyclo_len);
                            }
                            fa.shiftPositions(cyclo_len);
                            if (tmp.containsKey("furan")) {
                                curr_fa.shiftPositions(-1);
                            }

                            for (Entry<String, ArrayList<FunctionalGroup>> kv : fa.getFunctionalGroupsInternal().entrySet()) {
                                if (!curr_fa.getFunctionalGroupsInternal().containsKey(kv.getKey())) {
                                    curr_fa.getFunctionalGroupsInternal().put(kv.getKey(), new ArrayList<>());
                                }
                                for (FunctionalGroup func_group : kv.getValue()) {
                                    curr_fa.getFunctionalGroupsInternal().get(kv.getKey()).add(func_group);
                                }
                            }

                            curr_fa.setNumCarbon(cyclo_len + fa.getNumCarbon());

                            for (Entry<Integer, String> kv : fa.getDoubleBonds().getDoubleBondPositions().entrySet()) {
                                curr_fa.getDoubleBonds().getDoubleBondPositions().put(kv.getKey() + cyclo_len, kv.getValue());
                            }
                            curr_fa.getDoubleBonds().setNumDoubleBonds(curr_fa.getDoubleBonds().getDoubleBondPositions().size());

                            if (!tmp.containsKey("tetrahydrofuran") && tmp.containsKey("furan")) {
                                curr_fa.getDoubleBonds().setNumDoubleBonds(curr_fa.getDoubleBonds().getNumDoubleBonds() + 2);
                                if (!curr_fa.getDoubleBonds().getDoubleBondPositions().containsKey(1)) {
                                    curr_fa.getDoubleBonds().getDoubleBondPositions().put(1, "E");
                                }
                                if (!curr_fa.getDoubleBonds().getDoubleBondPositions().containsKey(3)) {
                                    curr_fa.getDoubleBonds().getDoubleBondPositions().put(3, "E");
                                }
                            }

                            tmp.put("cyclo_yl", true);
                        } else {
                            // add carbon chains here here
                            // special chains: i.e. ethyl, methyl
                            String fg_name = "";
                            if (fa.getDoubleBonds().getNumDoubleBonds() == 0 && fa.getFunctionalGroupsInternal().isEmpty()) {
                                FunctionalGroup fg = null;
                                if (fa.getNumCarbon() == 1) {
                                    fg_name = "Me";
                                    fg = knownFunctionalGroups.get(fg_name);
                                } else if (fa.getNumCarbon() == 2) {
                                    fg_name = "Et";
                                    fg = knownFunctionalGroups.get(fg_name);
                                }
                                if (fg != null && fg_name.length() > 0) {
                                    fg.setPosition(fa.getPosition());
                                    if (!curr_fa.getFunctionalGroupsInternal().containsKey(fg_name)) {
                                        curr_fa.getFunctionalGroupsInternal().put(fg_name, new ArrayList<>());
                                    }
                                    curr_fa.getFunctionalGroupsInternal().get(fg_name).add(fg);
                                }
                            }
                            if (fg_name.length() == 0) {
                                CarbonChain cc = new CarbonChain(fa, fa.getPosition(), knownFunctionalGroups);
                                if (!curr_fa.getFunctionalGroupsInternal().containsKey("cc")) {
                                    curr_fa.getFunctionalGroupsInternal().put("cc", new ArrayList<>());
                                }
                                curr_fa.getFunctionalGroupsInternal().get("cc").add(cc);
                            }
                        }
                    }
                    if (tmp.containsKey("cyclo")) {
                        tmp.remove("cyclo");
                    }
                    curr_fa.getFunctionalGroupsInternal().remove(yl);
                }
            }
        }

        if (curr_fa.getFunctionalGroupsInternal().containsKey("cyclo")) {
            FattyAcid fa = (FattyAcid) curr_fa.getFunctionalGroupsInternal().get("cyclo").get(0);
            curr_fa.getFunctionalGroupsInternal().remove("cyclo");
            if (!tmp.containsKey("cyclo_len")) {
                tmp.put("cyclo_len", 5);
            }
            int start_pos = curr_fa.getNumCarbon() + 1;
            int end_pos = curr_fa.getNumCarbon() + (int) tmp.get("cyclo_len");
            fa.shiftPositions(start_pos - 1);

            if (curr_fa.getFunctionalGroupsInternal().containsKey("cy")) {
                for (FunctionalGroup cy : curr_fa.getFunctionalGroupsInternal().get("cy")) {
                    cy.shiftPositions(start_pos - 1);
                }
            }
            for (Entry<String, ArrayList<FunctionalGroup>> kv : fa.getFunctionalGroupsInternal().entrySet()) {
                if (!curr_fa.getFunctionalGroupsInternal().containsKey(kv.getKey())) {
                    curr_fa.getFunctionalGroupsInternal().put(kv.getKey(), new ArrayList<>());
                }
                for (FunctionalGroup func_group : kv.getValue()) {
                    curr_fa.getFunctionalGroupsInternal().get(kv.getKey()).add(func_group);
                }
            }

            for (Entry<Integer, String> kv : fa.getDoubleBonds().getDoubleBondPositions().entrySet()) {
                curr_fa.getDoubleBonds().getDoubleBondPositions().put(kv.getKey() + start_pos - 1, kv.getValue());
            }
            curr_fa.getDoubleBonds().setNumDoubleBonds(curr_fa.getDoubleBonds().getDoubleBondPositions().size());

            if (!tmp.containsKey("tetrahydrofuran") && tmp.containsKey("furan")) {
                curr_fa.getDoubleBonds().setNumDoubleBonds(curr_fa.getDoubleBonds().getNumDoubleBonds() + 2);
                if (!curr_fa.getDoubleBonds().getDoubleBondPositions().containsKey(1 + curr_fa.getNumCarbon())) {
                    curr_fa.getDoubleBonds().getDoubleBondPositions().put(1 + curr_fa.getNumCarbon(), "E");
                }
                if (!curr_fa.getDoubleBonds().getDoubleBondPositions().containsKey(3 + curr_fa.getNumCarbon())) {
                    curr_fa.getDoubleBonds().getDoubleBondPositions().put(3 + curr_fa.getNumCarbon(), "E");
                }
            }

            curr_fa.setNumCarbon(curr_fa.getNumCarbon() + fa.getNumCarbon());

            tmp.put("fg_pos", new GenericList());
            GenericList l1 = new GenericList();
            l1.add(start_pos);
            l1.add("");
            ((GenericList) tmp.get("fg_pos")).add(l1);
            GenericList l2 = new GenericList();
            l2.add(end_pos);
            l2.add("");
            ((GenericList) tmp.get("fg_pos")).add(l2);

            add_cyclo(node);

            if (tmp.containsKey("cyclo_len")) {
                tmp.remove("cyclo_len");
            }
            if (tmp.containsKey("cyclo")) {
                tmp.remove("cyclo");
            }
        } else if (tmp.containsKey("cyclo")) {
            tmp.put("cyclo_yl", 1);
            tmp.put("cyclo_len", curr_fa.getNumCarbon());
            tmp.put("fg_pos", new GenericList());
            GenericList l1 = new GenericList();
            l1.add(1);
            l1.add("");
            ((GenericList) tmp.get("fg_pos")).add(l1);
            GenericList l2 = new GenericList();
            l2.add(curr_fa.getNumCarbon());
            l2.add("");
            ((GenericList) tmp.get("fg_pos")).add(l2);

            tmp.remove("cyclo");
        }

        tmp.put("length_pattern", "");
        tmp.put("length_tokens", new GenericList());
        tmp.put("add_lengths", 0);
    }

    private void switch_position(FunctionalGroup func_group, int switch_num) {
        func_group.setPosition(switch_num - func_group.getPosition());
        for (Entry<String, ArrayList<FunctionalGroup>> kv : func_group.getFunctionalGroupsInternal().entrySet()) {
            for (FunctionalGroup fg : kv.getValue()) {
                switch_position(fg, switch_num);
            }
        }
    }

    private void add_cyclo(TreeNode node) {
        int start = (int) ((GenericList) ((GenericList) tmp.get("fg_pos")).get(0)).get(0);
        int end = (int) ((GenericList) ((GenericList) tmp.get("fg_pos")).get(1)).get(0);

        DoubleBonds cyclo_db = new DoubleBonds();
        // check double bonds
        if (fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().size() > 0) {
            for (Entry<Integer, String> kv : fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().entrySet()) {
                if (start <= kv.getKey() && kv.getKey() <= end) {
                    cyclo_db.getDoubleBondPositions().put(kv.getKey(), kv.getValue());
                }
            }
            cyclo_db.setNumDoubleBonds(cyclo_db.getDoubleBondPositions().size());

            for (Entry<Integer, String> kv : cyclo_db.getDoubleBondPositions().entrySet()) {
                fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().remove(kv.getKey());
            }
            fattyAcylStack.peekLast().getDoubleBonds().setNumDoubleBonds(fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().size());

        }
        // check functionalGroups
        HashMap<String, ArrayList<FunctionalGroup>> cyclo_fg = new HashMap<>();
        HashSet<String> remove_list = new HashSet<>();
        FattyAcid curr_fa = fattyAcylStack.peekLast();

        if (curr_fa.getFunctionalGroupsInternal().containsKey("noyloxy")) {
            ArrayList<Integer> remove_item = new ArrayList<>();
            int i = 0;
            for (FunctionalGroup func_group : curr_fa.getFunctionalGroupsInternal().get("noyloxy")) {
                if (start <= func_group.getPosition() && func_group.getPosition() <= end) {
                    CarbonChain cc = new CarbonChain((FattyAcid) func_group, func_group.getPosition(), knownFunctionalGroups);

                    if (!curr_fa.getFunctionalGroupsInternal().containsKey("cc")) {
                        curr_fa.getFunctionalGroupsInternal().put("cc", new ArrayList<>());
                    }
                    curr_fa.getFunctionalGroupsInternal().get("cc").add(cc);
                    remove_item.add(i);
                }
                ++i;
            }
            for (int ii = remove_item.size() - 1; ii >= 0; --ii) {
                curr_fa.getFunctionalGroupsInternal().get("noyloxy").remove((int) remove_item.get(ii));
            }
            if (curr_fa.getFunctionalGroupsInternal().get("noyloxy").isEmpty()) {
                remove_list.add("noyloxy");
            }
        }

        for (Entry<String, ArrayList<FunctionalGroup>> kv : curr_fa.getFunctionalGroupsInternal().entrySet()) {
            ArrayList<Integer> remove_item = new ArrayList<>();
            int i = 0;
            for (FunctionalGroup func_group : kv.getValue()) {
                if (start <= func_group.getPosition() && func_group.getPosition() <= end) {
                    if (!cyclo_fg.containsKey(kv.getKey())) {
                        cyclo_fg.put(kv.getKey(), new ArrayList<>());
                    }
                    cyclo_fg.get(kv.getKey()).add(func_group);
                    remove_item.add(i);
                }
                ++i;
            }
            for (int ii = remove_item.size() - 1; ii >= 0; --ii) {
                kv.getValue().remove((int) remove_item.get(ii));
            }
            if (kv.getValue().isEmpty()) {
                remove_list.add(kv.getKey());
            }
        }
        for (String fg : remove_list) {
            curr_fa.getFunctionalGroupsInternal().remove(fg);
        }

        ArrayList<Element> bridge_chain = new ArrayList<>();
        if (tmp.containsKey("furan")) {
            tmp.remove("furan");
            bridge_chain.add(Element.O);
        }

        Cycle cycle = new Cycle(end - start + 1 + bridge_chain.size(), start, end, cyclo_db, cyclo_fg, bridge_chain, knownFunctionalGroups);
        if (!fattyAcylStack.peekLast().getFunctionalGroupsInternal().containsKey("cy")) {
            fattyAcylStack.peekLast().getFunctionalGroupsInternal().put("cy", new ArrayList<>());
        }
        fattyAcylStack.peekLast().getFunctionalGroupsInternal().get("cy").add(cycle);
    }

    private void add_wax_ester(TreeNode node) {
        FattyAcid fa = fattyAcylStack.pollLast();
        fa.setLipidFaBondType(LipidFaBondType.ETHER);
        fattyAcylStack.addFirst(fa);
    }

    private void set_methyl(TreeNode node) {
        fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() + 1);
    }

    private void set_acetic_acid(TreeNode node) {
        fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() + 2);
        headgroup = "FA";
    }

    private void set_methylene(TreeNode node) {
        tmp.put("fg_type", "methylene");
        GenericList gl = (GenericList) tmp.get("fg_pos");
        if (gl.size() > 1) {
            if ((int) ((GenericList) gl.get(0)).get(0) < (int) ((GenericList) gl.get(1)).get(0)) {
                ((GenericList) gl.get(1)).set(0, (int) ((GenericList) gl.get(1)).get(0) + 1);
            } else if ((int) ((GenericList) gl.get(0)).get(0) > (int) ((GenericList) gl.get(1)).get(0)) {
                ((GenericList) gl.get(0)).set(0, (int) ((GenericList) gl.get(0)).get(0) + 1);
            }
            fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() + 1);
            tmp.put("add_methylene", 1);
        }
    }

    private void set_car(TreeNode node) {
        tmp.put("fg_pos", new GenericList());
        tmp.put("fg_type", "");
    }

    private void add_car(TreeNode node) {
        headgroup = "CAR";
    }

    private void add_ethanolamine(TreeNode node) {
        headgroup = "NAE";
    }

    private void add_amine_name(TreeNode node) {
        headgroup = "NA";
    }

    private void reset_length(TreeNode node) {
        tmp.put("length", 0);
        tmp.put("length_pattern", "");
        tmp.put("length_tokens", new GenericList());
        tmp.put("add_lengths", 1);
    }

    private void set_fatty_length(TreeNode node) {
        tmp.put("add_lengths", 0);
    }

    private void last_number(TreeNode node) {
        if ((int) tmp.get("add_lengths") == 1) {
            tmp.put("length", (int) tmp.get("length") + LAST_NUMBERS.get(node.getText()));
            tmp.put("length_pattern", (String) tmp.get("length_pattern") + "L");
            ((GenericList) tmp.get("length_tokens")).add(LAST_NUMBERS.get(node.getText()));
        }
    }

    private void second_number(TreeNode node) {
        if ((int) tmp.get("add_lengths") == 1) {
            tmp.put("length", (int) tmp.get("length") + SECOND_NUMBERS.get(node.getText()));
            tmp.put("length_pattern", (String) tmp.get("length_pattern") + "S");
            ((GenericList) tmp.get("length_tokens")).add(SECOND_NUMBERS.get(node.getText()));
        }
    }

    private void special_number(TreeNode node) {
        if ((int) tmp.get("add_lengths") == 1) {
            tmp.put("length", (int) tmp.get("length") + SPECIAL_NUMBERS.get(node.getText()));
            tmp.put("length_pattern", (String) tmp.get("length_pattern") + "X");
            ((GenericList) tmp.get("length_tokens")).add(SPECIAL_NUMBERS.get(node.getText()));
        }
    }

    private void set_iso(TreeNode node) {
        FattyAcid curr_fa = fattyAcylStack.peekLast();
        curr_fa.setNumCarbon(curr_fa.getNumCarbon() - 1);
        FunctionalGroup fg = knownFunctionalGroups.get("Me");
        fg.setPosition(2);
        if (!curr_fa.getFunctionalGroupsInternal().containsKey("Me")) {
            curr_fa.getFunctionalGroupsInternal().put("Me", new ArrayList<>());
        }
        curr_fa.getFunctionalGroupsInternal().get("Me").add(fg);
    }

    private void set_prosta(TreeNode node) {
        int minus_pos = 0;
        if (tmp.containsKey("reduction")) {
            for (Object o : (GenericList) tmp.get("reduction")) {
                int i = (int) o;
                minus_pos += i < 8 ? 1 : 0;
            }
        }

        tmp.put("fg_pos", new GenericList());
        GenericList l1 = new GenericList();
        l1.add(8 - minus_pos);
        l1.add("");

        GenericList l2 = new GenericList();
        l2.add(12 - minus_pos);
        l2.add("");

        ((GenericList) tmp.get("fg_pos")).add(l1);
        ((GenericList) tmp.get("fg_pos")).add(l2);
        tmp.put("fg_type", "cy");
    }

    private void set_tetrahydrofuran(TreeNode node) {
        tmp.put("furan", 1);
        tmp.put("tetrahydrofuran", 1);
        set_cycle(node);
    }

    private void set_furan(TreeNode node) {
        tmp.put("furan", 1);
        set_cycle(node);
    }

    private void check_db(TreeNode node) {
        String fa_i = FA_I();
        FattyAcid curr_fa = fattyAcylStack.peekLast();
        if (((Dictionary) tmp.get(fa_i)).containsKey("fg_pos_summary")) {
            for (Entry<String, Object> kv : ((Dictionary) ((Dictionary) tmp.get(fa_i)).get("fg_pos_summary")).entrySet()) {
                int k = Integer.valueOf(kv.getKey());
                String v = (String) ((Dictionary) ((Dictionary) tmp.get(fa_i)).get("fg_pos_summary")).get(kv.getKey());
                if (k > 0 && !curr_fa.getDoubleBonds().getDoubleBondPositions().containsKey(k) && (v.equals("E") || v.equals("Z") || v.length() == 0)) {
                    curr_fa.getDoubleBonds().getDoubleBondPositions().put(k, v);
                    curr_fa.getDoubleBonds().setNumDoubleBonds(curr_fa.getDoubleBonds().getDoubleBondPositions().size());
                }
            }
        }
    }

    private void set_coa(TreeNode node) {
        headgroup = "CoA";
    }

    private void set_yl_ending(TreeNode node) {
        int l = Integer.valueOf(node.getText()) - 1;
        if (l == 0) {
            return;
        }

        FattyAcid curr_fa = fattyAcylStack.peekLast();

        if (tmp.containsKey("furan")) {
            curr_fa.setNumCarbon(curr_fa.getNumCarbon() - l);
            return;
        }

        String fname = "";
        FunctionalGroup fg = null;
        if (l == 1) {
            fname = "Me";
            fg = knownFunctionalGroups.get(fname);
        } else if (l == 2) {
            fname = "Et";
            fg = knownFunctionalGroups.get(fname);
        } else {
            FattyAcid fa = new FattyAcid("FA", l, knownFunctionalGroups);
            // shift functional groups
            for (Entry<String, ArrayList<FunctionalGroup>> kv : curr_fa.getFunctionalGroupsInternal().entrySet()) {
                ArrayList<Integer> remove_item = new ArrayList<>();
                int i = 0;
                for (FunctionalGroup func_group : kv.getValue()) {
                    if (func_group.getPosition() <= l) {
                        remove_item.add(i);
                        if (!fa.getFunctionalGroupsInternal().containsKey(kv.getKey())) {
                            fa.getFunctionalGroupsInternal().put(kv.getKey(), new ArrayList<>());
                        }
                        func_group.setPosition(l + 1 - func_group.getPosition());
                        fa.getFunctionalGroupsInternal().get(kv.getKey()).add(func_group);
                    }
                }
                for (int ii = remove_item.size() - 1; ii >= 0; --ii) {
                    curr_fa.getFunctionalGroupsInternal().get(kv.getKey()).remove((int) remove_item.get(ii));
                }
            }
            Map<String, ArrayList<FunctionalGroup>> func_dict = curr_fa.getFunctionalGroupsInternal();
            curr_fa.setFunctionalGroups(new HashMap<>());
            for (Entry<String, ArrayList<FunctionalGroup>> kv : func_dict.entrySet()) {
                if (kv.getValue().size() > 0) {
                    curr_fa.getFunctionalGroupsInternal().put(kv.getKey(), kv.getValue());
                }
            }

            // shift double bonds
            if (curr_fa.getDoubleBonds().getDoubleBondPositions().size() > 0) {
                fa.setDoubleBonds(new DoubleBonds());
                for (Entry<Integer, String> kv : curr_fa.getDoubleBonds().getDoubleBondPositions().entrySet()) {
                    if (kv.getKey() <= l) {
                        fa.getDoubleBonds().getDoubleBondPositions().put(l + 1 - kv.getKey(), kv.getValue());
                    }
                }
                fa.getDoubleBonds().setNumDoubleBonds(fa.getDoubleBonds().getDoubleBondPositions().size());
                for (Entry<Integer, String> kv : fa.getDoubleBonds().getDoubleBondPositions().entrySet()) {
                    curr_fa.getDoubleBonds().getDoubleBondPositions().remove(kv.getKey());
                }
            }
            fname = "cc";
            fg = new CarbonChain(fa, knownFunctionalGroups);
        }
        curr_fa.setNumCarbon(curr_fa.getNumCarbon() - l);
        fg.setPosition(l);
        curr_fa.shiftPositions(-l);
        if (!curr_fa.getFunctionalGroupsInternal().containsKey(fname)) {
            curr_fa.getFunctionalGroupsInternal().put(fname, new ArrayList<>());
        }
        curr_fa.getFunctionalGroupsInternal().get(fname).add(fg);
    }

    private void set_dial(TreeNode node) {
        FattyAcid curr_fa = fattyAcylStack.peekLast();
        int pos = curr_fa.getNumCarbon();
        FunctionalGroup fg = knownFunctionalGroups.get("oxo");
        fg.setPosition(pos);
        if (!curr_fa.getFunctionalGroupsInternal().containsKey("oxo")) {
            curr_fa.getFunctionalGroupsInternal().put("oxo", new ArrayList<>());
        }
        curr_fa.getFunctionalGroupsInternal().get("oxo").add(fg);
    }

    private void open_db_length(TreeNode node) {
        tmp.put("add_lengths", 1);
    }

    private void close_db_length(TreeNode node) {
        tmp.put("add_lengths", 0);
    }

    private void set_dioic(TreeNode node) {
        headgroup = "FA";
        int pos = (((GenericList) tmp.get("fg_pos")).size() == 2) ? (int) ((GenericList) ((GenericList) tmp.get("fg_pos")).get(1)).get(0) : fattyAcylStack.peekLast().getNumCarbon();
        if (tmp.containsKey("reduction")) {
            pos -= ((GenericList) tmp.get("reduction")).size();
        }
        fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() - 1);
        FunctionalGroup func_group = knownFunctionalGroups.get("COOH");
        func_group.setPosition(pos - 1);
        if (!fattyAcylStack.peekLast().getFunctionalGroupsInternal().containsKey("COOH")) {
            fattyAcylStack.peekLast().getFunctionalGroupsInternal().put("COOH", new ArrayList<>());
        }
        fattyAcylStack.peekLast().getFunctionalGroupsInternal().get("COOH").add(func_group);
    }

    private void setup_hydroxyl(TreeNode node) {
        tmp.put("hydroxyl_pos", new GenericList());
    }

    private void add_hydroxyls(TreeNode node) {
        if (((GenericList) tmp.get("hydroxyl_pos")).size() > 1) {
            FunctionalGroup fg_oh = knownFunctionalGroups.get("OH");
            ArrayList<Integer> sorted_pos = new ArrayList<>();
            for (Object o : (GenericList) tmp.get("hydroxyl_pos")) {
                int i = (int) o;
                sorted_pos.add(i);
            }
            Collections.sort(sorted_pos, (Integer a, Integer b) -> b.compareTo(a));
            for (int i = 0; i < sorted_pos.size() - 1; ++i) {
                int pos = sorted_pos.get(i);
                FunctionalGroup fg_insert = fg_oh.copy();
                fg_insert.setPosition(pos);
                if (!fattyAcylStack.peekLast().getFunctionalGroupsInternal().containsKey("OH")) {
                    fattyAcylStack.peekLast().getFunctionalGroupsInternal().put("OH", new ArrayList<>());
                }
                fattyAcylStack.peekLast().getFunctionalGroupsInternal().get("OH").add(fg_insert);
            }
        }
    }

    private void set_ate(TreeNode node) {
        fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() + ATE.get(node.getText()));
        headgroup = "WE";
    }

    private void add_hydroxyl(TreeNode node) {
        int h = Integer.valueOf(node.getText());
        ((GenericList) tmp.get("hydroxyl_pos")).add(h);
    }

    private void set_functional_group(TreeNode node) {
        tmp.put("fg_pos", new GenericList());
        tmp.put("fg_type", "");
    }

    private void add_functional_group(TreeNode node) {
        if (tmp.containsKey("added_func_group")) {
            tmp.remove("added_func_group");
            return;
        } else if (tmp.containsKey("add_methylene")) {
            tmp.remove("add_methylene");
            add_cyclo(node);
            return;
        }

        String t = (String) tmp.get("fg_type");

        FunctionalGroup fg = null;
        if (!t.equals("acetoxy")) {
            if (!FUNC_GROUPS.containsKey(t)) {
                throw new LipidException("Unknown functional group: '" + t + "'");
            }
            t = FUNC_GROUPS.get(t);
            if (t.length() == 0) {
                return;
            }
            fg = knownFunctionalGroups.get(t);
        } else {
            fg = new AcylAlkylGroup(new FattyAcid("O", 2, knownFunctionalGroups), knownFunctionalGroups);
        }

        FattyAcid fa = fattyAcylStack.peekLast();
        if (!fa.getFunctionalGroupsInternal().containsKey(t)) {
            fa.getFunctionalGroupsInternal().put(t, new ArrayList<>());
        }
        int l = ((GenericList) tmp.get("fg_pos")).size();
        for (Object o : (GenericList) tmp.get("fg_pos")) {
            GenericList lst = (GenericList) o;
            int pos = (int) lst.get(0);

            int num_pos = 0;
            if (tmp.containsKey("reduction")) {
                for (Object oo : (GenericList) tmp.get("reduction")) {
                    int i = (int) oo;
                    num_pos += i < pos ? 1 : 0;
                }
            }
            FunctionalGroup fg_insert = fg.copy();
            fg_insert.setPosition(pos - num_pos);
            fa.getFunctionalGroupsInternal().get(t).add(fg_insert);
        }
    }

    private void set_double_bond_information(TreeNode node) {
        ((Dictionary) tmp.get(FA_I())).put("db_position", 0);
        ((Dictionary) tmp.get(FA_I())).put("db_cistrans", "");
    }

    private void add_double_bond_information(TreeNode node) {
        int pos = (int) ((Dictionary) tmp.get(FA_I())).get("db_position");
        String str_pos = Integer.toString(pos);
        String cistrans = (String) ((Dictionary) tmp.get(FA_I())).get("db_cistrans");
        if (cistrans.length() == 0 && ((Dictionary) tmp.get(FA_I())).containsKey("fg_pos_summary") && ((Dictionary) ((Dictionary) tmp.get(FA_I())).get("fg_pos_summary")).containsKey(str_pos)) {
            cistrans = (String) ((Dictionary) ((Dictionary) tmp.get(FA_I())).get("fg_pos_summary")).get(str_pos);
        }
        if (pos == 0) {
            return;
        }

        cistrans = cistrans.toUpperCase();

        ((Dictionary) tmp.get(FA_I())).remove("db_position");
        ((Dictionary) tmp.get(FA_I())).remove("db_cistrans");

        if (!cistrans.equals("E") && !cistrans.equals("Z")) {
            cistrans = "";
        }
        if (!fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().containsKey(pos) || fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().get(pos).length() == 0) {
            fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().put(pos, cistrans);
            fattyAcylStack.peekLast().getDoubleBonds().setNumDoubleBonds(fattyAcylStack.peekLast().getDoubleBonds().getDoubleBondPositions().size());
        }
    }

    private void set_cistrans(TreeNode node) {
        ((Dictionary) tmp.get(FA_I())).put("db_cistrans", node.getText());
    }

    private void set_double_bond_position(TreeNode node) {
        int pos = Integer.valueOf(node.getText());
        int num_db = 0;
        if (tmp.containsKey("reduction")) {
            GenericList gl = (GenericList) tmp.get("reduction");
            int l = gl.size();
            for (int i = 0; i < l; ++i) {
                num_db += ((int) gl.get(i) < pos) ? 1 : 0;
            }
        }

        ((Dictionary) tmp.get(FA_I())).put("db_position", pos - num_db);
    }

    private void add_summary(TreeNode node) {
        String fa_i = FA_I();
        ((Dictionary) tmp.get(fa_i)).put("fg_pos_summary", new Dictionary());
        for (Object o : (GenericList) tmp.get("fg_pos")) {
            GenericList lst = (GenericList) o;
            String k = Integer.toString((int) lst.get(0));
            String v = ((String) lst.get(1)).toUpperCase();
            ((Dictionary) ((Dictionary) tmp.get(fa_i)).get("fg_pos_summary")).put(k, v);
        }
    }

    private void set_functional_length(TreeNode node) {
        if ((int) tmp.get("length") != ((GenericList) tmp.get("fg_pos")).size()) {
            throw new LipidException("Length of functional group '" + Integer.toString((int) tmp.get("length")) + "' does not match with number of its positions '" + Integer.toString(((GenericList) tmp.get("fg_pos")).size()) + "'");
        }
    }

    private void set_functional_type(TreeNode node) {
        tmp.put("fg_type", node.getText());
    }

    private void add_epoxy(TreeNode node) {
        GenericList gl = (GenericList) tmp.get("fg_pos");
        while (gl.size() > 1) {
            gl.remove(gl.size() - 1);
        }
        tmp.put("fg_type", "Epoxy");
    }

    private void set_functional_position(TreeNode node) {
        GenericList gl = new GenericList();
        gl.add(0);
        gl.add("");
        ((GenericList) tmp.get("fg_pos")).add(gl);
    }

    private void set_functional_pos(TreeNode node) {
        GenericList gl = (GenericList) tmp.get("fg_pos");
        ((GenericList) gl.get(gl.size() - 1)).set(0, Integer.valueOf(node.getText()));
    }

    private void add_func_stereo(TreeNode node) {
        int l = ((GenericList) tmp.get("fg_pos")).size();
        ((GenericList) ((GenericList) tmp.get("fg_pos")).get(l - 1)).set(1, node.getText());
    }

    private void reduction(TreeNode node) {
        int shift_len = -((GenericList) tmp.get("fg_pos")).size();
        fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() + shift_len);
        for (Entry<String, ArrayList<FunctionalGroup>> kv : fattyAcylStack.peekLast().getFunctionalGroupsInternal().entrySet()) {
            for (FunctionalGroup func_group : kv.getValue()) {
                func_group.shiftPositions(shift_len);
            }
        }

        tmp.put("reduction", new GenericList());
        for (Object o : (GenericList) tmp.get("fg_pos")) {
            GenericList lst = (GenericList) o;
            ((GenericList) tmp.get("reduction")).add((int) lst.get(0));
        }
    }

    private void homo(TreeNode node) {
        tmp.put("post_adding", new GenericList());
        for (Object o : (GenericList) tmp.get("fg_pos")) {
            GenericList lst = (GenericList) o;
            ((GenericList) tmp.get("post_adding")).add((int) lst.get(0));
        }
    }

    private void set_cycle(TreeNode node) {
        tmp.put("cyclo", 1);
    }

    private void rearrange_cycle(TreeNode node) {
        if (tmp.containsKey("post_adding")) {
            fattyAcylStack.peekLast().setNumCarbon(fattyAcylStack.peekLast().getNumCarbon() + ((GenericList) tmp.get("post_adding")).size());
            tmp.remove("post_adding");
        }

        FattyAcid curr_fa = fattyAcylStack.peekLast();
        int start = (int) ((GenericList) ((GenericList) tmp.get("fg_pos")).get(0)).get(0);
        if (curr_fa.getFunctionalGroupsInternal().containsKey("cy")) {
            for (FunctionalGroup cy : curr_fa.getFunctionalGroupsInternal().get("cy")) {
                int shift_val = start - cy.getPosition();
                if (shift_val == 0) {
                    continue;
                }
                ((Cycle) cy).rearrangeFunctionalGroups(curr_fa, shift_val);
            }
        }
    }

    private void set_recursion(TreeNode node) {
        tmp.put("fg_pos", new GenericList());
        tmp.put("fg_type", "");
        fattyAcylStack.add(new FattyAcid("FA", knownFunctionalGroups));
        tmp.put(FA_I(), new Dictionary());
        ((Dictionary) tmp.get(FA_I())).put("recursion_pos", 0);
    }

    private void add_recursion(TreeNode node) {
        int pos = (int) ((Dictionary) tmp.get(FA_I())).get("recursion_pos");
        FattyAcid fa = fattyAcylStack.pollLast();

        fa.setPosition(pos);
        FattyAcid curr_fa = fattyAcylStack.peekLast();

        String fname = "";
        if (tmp.containsKey("cyclo_yl")) {
            fname = "cyclo";
            tmp.remove("cyclo_yl");
        } else {
            fname = headgroup;
        }
        if (!curr_fa.getFunctionalGroupsInternal().containsKey(fname)) {
            curr_fa.getFunctionalGroupsInternal().put(fname, new ArrayList<>());
        }
        curr_fa.getFunctionalGroupsInternal().get(fname).add(fa);
        tmp.put("added_func_group", 1);
    }

    private void set_recursion_pos(TreeNode node) {
        ((Dictionary) tmp.get(FA_I())).put("recursion_pos", Integer.valueOf(node.getText()));
    }

}