<commit_before>/*
 * Copyright (c) 2001-2008 <NAME>
 * 418 Richmond St., El Cerrito, CA  94530
 * <EMAIL>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the author nor the names of any contributors may
 *       be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package edu.berkeley.compbio.ncbitaxonomy;

import com.davidsoergel.dsutils.CacheManager;
import com.davidsoergel.stats.ContinuousDistribution1D;
import com.davidsoergel.trees.AbstractRootedPhylogeny;
import com.davidsoergel.trees.BasicPhylogenyNode;
import com.davidsoergel.trees.BasicRootedPhylogeny;
import com.davidsoergel.trees.DepthFirstTreeIterator;
import com.davidsoergel.trees.HierarchyNode;
import com.davidsoergel.trees.NoSuchNodeException;
import com.davidsoergel.trees.NodeNamer;
import com.davidsoergel.trees.PhylogenyNode;
import com.davidsoergel.trees.RootedPhylogeny;
import com.davidsoergel.trees.TreeException;
import com.google.common.base.Function;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Multiset;
import edu.berkeley.compbio.phyloutils.PhyloUtilsException;
import edu.berkeley.compbio.phyloutils.PhyloUtilsRuntimeException;
import edu.berkeley.compbio.phyloutils.TaxonomyService;
import edu.berkeley.compbio.phyloutils.TaxonomySynonymService;
import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.NoResultException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Presents the NCBI taxonomy as an RootedPhylogeny with Integer IDs corresponding to the NCBI taxids.  This is a live
 * view onto a MySQL database imported from ftp://ftp.ncbi.nih.gov/pub/taxonomy/taxdump.tar.gz.  The database connection
 * is configured by placing a file at ~/.ncbitaxonomy/ncbitaxonomy.properties, containing something like this:
 * <p/>
 * <pre>
 * default = orchid_test
 * <p/>
 * localhost_test.driver = com.mysql.jdbc.Driver
 * localhost_test.url = jdbc:mysql://localhost/ncbi_tx_test
 * localhost_test.username = ncbi_tx_test
 * localhost_test.password = <PASSWORD>
 * <p/>
 * orchid_test.driver = com.mysql.jdbc.Driver
 * orchid_test.url = jdbc:mysql://orchid.berkeley.edu/ncbi_tx_test
 * orchid_test.username = ncbi_tx_test
 * orchid_test.password = <PASSWORD>
 * <p/>
 * </pre>
 * <p/>
 * Many methods required by the RootedPhylogeny interface are not appropriate for this purpose and hence throw
 * NotImplementedException (perhaps this is an indication that the interface should be refactored, e.g. separated into a
 * mutable vs. immutable varieties, etc.; oh well).
 * <p/>
 * Those methods that are functional are largely delegated to NcbiTaxonomyServiceImpl, which is instantiated here and
 * configured by Spring.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @version $Id$
 */
@Service(value = "ncbiTaxonomyPhylogeny")
public class NcbiTaxonomyPhylogeny extends AbstractRootedPhylogeny<Integer>
		implements TaxonomyService<Integer>, TaxonomySynonymService //extends Singleton<PhyloUtilsService> //,
	{
	private static final Logger logger = Logger.getLogger(NcbiTaxonomyPhylogeny.class);
	// ------------------------------ FIELDS ------------------------------

	private static NcbiTaxonomyPhylogeny instance;// = new NcbiTaxonomyService();

	@Autowired
	private NcbiTaxonomyServiceEngine ncbiTaxonomyServiceEngine;

	// -------------------------- STATIC METHODS --------------------------


	private Map<Integer, List<Integer>> ancestorPathIdsCache;

	@NotNull
	@Override
	public List<Integer> getAncestorPathIds(final Integer id) throws NoSuchNodeException
		{
		List<Integer> result = ancestorPathIdsCache.get(id);

		if (result == null || result.isEmpty())
			{
			if (result != null && result.isEmpty())
				{
				logger.warn("Cache contained empty ancestorPathIds for " + id);
				}

			PhylogenyNode<Integer> node = getNode(id);

			if (id == 1)
				{
				result = new ArrayList<Integer>(1);
				}
			else
				{
				PhylogenyNode<Integer> parent = node.getParent();

				if (parent == null || parent.equals(node))
					{
					throw new NcbiTaxonomyRuntimeException("Node " + id + " has invalid parent: " + parent);
					}

				result = new ArrayList<Integer>(getAncestorPathIds(parent.getPayload()));
				}

			result.add(id);

			result = Collections.unmodifiableList(result);

			assert !result.isEmpty();
			ancestorPathIdsCache.put(id, result);
			}
		return result;
		//return getNode(id).getAncestorPathIds();
		}

	private Map<Integer, BasicPhylogenyNode<Integer>> convertedNodes =
			new MapMaker().makeComputingMap(new Function<Integer, BasicPhylogenyNode<Integer>>()
			{
			public BasicPhylogenyNode<Integer> apply(final Integer id)
				{
				try
					{
					List<Integer> idPath = getAncestorPathIds(id);

					BasicPhylogenyNode<Integer> parent = null;
					if (idPath.size() > 1)
						{
						parent = convertedNodes.get(idPath.get(idPath.size() - 2));
						}

					BasicPhylogenyNode<Integer> convertedNode = new BasicPhylogenyNode<Integer>(parent, id);

					return convertedNode;
					}
				catch (NoSuchNodeException e)
					{
					throw new PhyloUtilsRuntimeException(e);
					}
				}
			});

	@NotNull
	@Override
	public List<BasicPhylogenyNode<Integer>> getAncestorPathAsBasic(final Integer id) throws NoSuchNodeException
		{
		return Collections.unmodifiableList(convertedNodes.get(id).getAncestorPath());
		}
/*
	private Map<Integer, List<BasicPhylogenyNode<Integer>>> ancestorPathNodesCache;
<commit_msg>aojaoj<commit_after>/*
 * Copyright (c) 2001-2008 <NAME>
 * 418 Richmond St., El Cerrito, CA  94530
 * <EMAIL>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the author nor the names of any contributors may
 *       be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package edu.berkeley.compbio.ncbitaxonomy;

import com.davidsoergel.dsutils.CacheManager;
import com.davidsoergel.stats.ContinuousDistribution1D;
import com.davidsoergel.trees.AbstractRootedPhylogeny;
import com.davidsoergel.trees.BasicPhylogenyNode;
import com.davidsoergel.trees.BasicRootedPhylogeny;
import com.davidsoergel.trees.DepthFirstTreeIterator;
import com.davidsoergel.trees.HierarchyNode;
import com.davidsoergel.trees.NoSuchNodeException;
import com.davidsoergel.trees.NodeNamer;
import com.davidsoergel.trees.PhylogenyNode;
import com.davidsoergel.trees.RootedPhylogeny;
import com.davidsoergel.trees.TreeException;
import com.google.common.base.Function;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Multiset;
import edu.berkeley.compbio.phyloutils.PhyloUtilsException;
import edu.berkeley.compbio.phyloutils.PhyloUtilsRuntimeException;
import edu.berkeley.compbio.phyloutils.TaxonomyService;
import edu.berkeley.compbio.phyloutils.TaxonomySynonymService;
import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.NoResultException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Presents the NCBI taxonomy as an RootedPhylogeny with Integer IDs corresponding to the NCBI taxids.  This is a live
 * view onto a MySQL database imported from ftp://ftp.ncbi.nih.gov/pub/taxonomy/taxdump.tar.gz.  The database connection
 * is configured by placing a file at ~/.ncbitaxonomy/ncbitaxonomy.properties, containing something like this:
 * <p/>
 * <pre>
 * default = orchid_test
 * <p/>
 * localhost_test.driver = com.mysql.jdbc.Driver
 * localhost_test.url = jdbc:mysql://localhost/ncbi_tx_test
 * localhost_test.username = ncbi_tx_test
 * localhost_test.password = <PASSWORD>
 * <p/>
 * orchid_test.driver = com.mysql.jdbc.Driver
 * orchid_test.url = jdbc:mysql://orchid.berkeley.edu/ncbi_tx_test
 * orchid_test.username = ncbi_tx_test
 * orchid_test.password = <PASSWORD>
 * <p/>
 * </pre>
 * <p/>
 * Many methods required by the RootedPhylogeny interface are not appropriate for this purpose and hence throw
 * NotImplementedException (perhaps this is an indication that the interface should be refactored, e.g. separated into a
 * mutable vs. immutable varieties, etc.; oh well).
 * <p/>
 * Those methods that are functional are largely delegated to NcbiTaxonomyServiceImpl, which is instantiated here and
 * configured by Spring.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @version $Id$
 */
@Service(value = "ncbiTaxonomyPhylogeny")
public class NcbiTaxonomyPhylogeny extends AbstractRootedPhylogeny<Integer>
		implements TaxonomyService<Integer>, TaxonomySynonymService //extends Singleton<PhyloUtilsService> //,
	{
	private static final Logger logger = Logger.getLogger(NcbiTaxonomyPhylogeny.class);
	// ------------------------------ FIELDS ------------------------------

	private static NcbiTaxonomyPhylogeny instance;// = new NcbiTaxonomyService();

	@Autowired
	private NcbiTaxonomyServiceEngine ncbiTaxonomyServiceEngine;

	// -------------------------- STATIC METHODS --------------------------


	private Map<Integer, List<Integer>> ancestorPathIdsCache;

	@NotNull
	@Override
	public List<Integer> getAncestorPathIds(final Integer id) throws NoSuchNodeException
		{
		List<Integer> result = ancestorPathIdsCache.get(id);

		if (result == null || result.isEmpty())
			{
			if (result != null && result.isEmpty())
				{
				logger.warn("Cache contained empty ancestorPathIds for " + id);
				}

			PhylogenyNode<Integer> node = getNode(id);

			if (id == 1)
				{
				result = new ArrayList<Integer>(1);
				}
			else
				{
				PhylogenyNode<Integer> parent = node.getParent();

				if (parent == null || parent.equals(node))
					{
					throw new NcbiTaxonomyRuntimeException("Node " + id + " has invalid parent: " + parent);
					}

				result = new ArrayList<Integer>(getAncestorPathIds(parent.getPayload()));
				}

			result.add(id);

			result = Collections.unmodifiableList(result);

			assert !result.isEmpty();
			ancestorPathIdsCache.put(id, result);
			}
		return result;
		//return getNode(id).getAncestorPathIds();
		}

	private Map<Integer, BasicPhylogenyNode<Integer>> convertedNodes =
			new MapMaker().makeComputingMap(new Function<Integer, BasicPhylogenyNode<Integer>>()
			{
			public BasicPhylogenyNode<Integer> apply(final Integer id)
				{
				try
					{
					List<Integer> idPath = getAncestorPathIds(id);

					BasicPhylogenyNode<Integer> parent = null;
					if (idPath.size() > 1)
						{
						parent = convertedNodes.get(idPath.get(idPath.size() - 2));
						}

					BasicPhylogenyNode<Integer> convertedNode = new BasicPhylogenyNode<Integer>(parent, id);
					convertedNode.setWeight(1.0);

					return convertedNode;
					}
				catch (NoSuchNodeException e)
					{
					throw new PhyloUtilsRuntimeException(e);
					}
				}
			});

	@NotNull
	@Override
	public List<BasicPhylogenyNode<Integer>> getAncestorPathAsBasic(final Integer id) throws NoSuchNodeException
		{
		return Collections.unmodifiableList(convertedNodes.get(id).getAncestorPath());
		}
/*
	private Map<Integer, List<BasicPhylogenyNode<Integer>>> ancestorPathNodesCache;

{'fraction_non_alphanumeric': 0.10613564451781538, 'fraction_numerical': 0.003164121612326317, 'mean_word_length': 4.205513784461153, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 87, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5222124', 'n_tokens_mistral': 4800, 'n_tokens_neox': 4227, 'n_words': 1169}