<commit_before>package name.abuchen.portfolio.money;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Singleton;

import org.eclipse.e4.core.di.annotations.Creatable;

import name.abuchen.portfolio.money.impl.ChainedExchangeRateTimeSeries;
import name.abuchen.portfolio.money.impl.InverseExchangeRateTimeSeries;

@Singleton
@Creatable
public class ExchangeRateProviderFactory
{
    private class Dijkstra
    {
        private List<ExchangeRateTimeSeries> timeSeries = new ArrayList<>();

        private Set<String> visited = new HashSet<>();
        private Set<String> unvisited = new HashSet<>();
        private Map<String, ExchangeRateTimeSeries> predecessors = new HashMap<>();
        private Map<String, Integer> distance = new HashMap<>();

        public Dijkstra(List<ExchangeRateTimeSeries> availableTimeSeries, String baseCurrency)
        {
            availableTimeSeries.stream().forEach(ts -> {
                timeSeries.add(ts);
                timeSeries.add(new InverseExchangeRateTimeSeries(ts));
            });

            distance.put(baseCurrency, 0);

            unvisited = CurrencyUnit.getAvailableCurrencyUnits().stream().map(CurrencyUnit::getCurrencyCode)
                            .collect(Collectors.toSet());

            if (!unvisited.contains(baseCurrency))
                return;

            while (!unvisited.isEmpty())
            {
                String node = getNodeWithMinimumDistance(unvisited);
                unvisited.remove(node);
                visited.add(node);

                for (ExchangeRateTimeSeries neighbor : getNeighbors(node))
                {
                    String neighborNode = neighbor.getTermCurrency();
                    int alternativeDistance = getDistance(node) + neighbor.getWeight();

                    if (alternativeDistance < getDistance(neighborNode))
                    {
                        distance.put(neighborNode, alternativeDistance);
                        predecessors.put(neighborNode, neighbor);
                    }
                }
            }
        }

        public List<ExchangeRateTimeSeries> findShortestPath(String termCurrency)
        {
            ExchangeRateTimeSeries current = predecessors.get(termCurrency);
            if (current == null)
                return Collections.emptyList();

            LinkedList<ExchangeRateTimeSeries> answer = new LinkedList<>();
            while (current != null)
            {
                answer.addFirst(current);
                current = predecessors.get(current.getBaseCurrency());
            }

            return answer;
        }

        private List<ExchangeRateTimeSeries> getNeighbors(String node)
        {
            return timeSeries.stream()
                            .filter(ts -> ts.getBaseCurrency().equals(node) && !visited.contains(ts.getTermCurrency()))
                            .collect(Collectors.toList());
        }

        private String getNodeWithMinimumDistance(Set<String> currencies)
        {
            return currencies.stream().min(Comparator.comparingInt(this::getDistance))
                            .orElse(currencies.iterator().next());
        }

        private int getDistance(String currency)
        {
            Integer d = distance.get(currency);
            return d == null ? Integer.MAX_VALUE : d;
        }
    }

    private static class CurrencyPair
    {
        private final String base;
        private final String term;

        public CurrencyPair(String base, String term)
        {
            this.base = base;
            this.term = term;
        }

        @Override
        public int hashCode()
        {
            return Objects.hash(base, term);
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;

            CurrencyPair other = (CurrencyPair) obj;

            if (!Objects.equals(base, other.base))
                return false;

            return Objects.equals(term, other.term);
        }

    }

    private final List<ExchangeRateProvider> providers;
    private final Map<CurrencyPair, ExchangeRateTimeSeries> cache = new HashMap<>();

    public ExchangeRateProviderFactory()
    {
        providers = new ArrayList<>();
        Iterator<ExchangeRateProvider> registeredProvider = ServiceLoader.load(ExchangeRateProvider.class).iterator();
        while (registeredProvider.hasNext())
        {
            ExchangeRateProvider provider = registeredProvider.next();
            providers.add(provider);
        }
    }

    public List<ExchangeRateProvider> getProviders()
    {
        return Collections.unmodifiableList(providers);
    }

    public List<ExchangeRateTimeSeries> getAvailableTimeSeries()
    {
        List<ExchangeRateTimeSeries> series = new ArrayList<>();
        for (ExchangeRateProvider p : providers)
            series.addAll(p.getAvailableTimeSeries());
        return series;
    }
<commit_msg>fix rare ConcurrentModificationException<commit_after>package name.abuchen.portfolio.money;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import javax.inject.Singleton;

import org.eclipse.e4.core.di.annotations.Creatable;

import name.abuchen.portfolio.money.impl.ChainedExchangeRateTimeSeries;
import name.abuchen.portfolio.money.impl.InverseExchangeRateTimeSeries;

@Singleton
@Creatable
public class ExchangeRateProviderFactory
{
    private class Dijkstra
    {
        private List<ExchangeRateTimeSeries> timeSeries = new ArrayList<>();

        private Set<String> visited = new HashSet<>();
        private Set<String> unvisited = new HashSet<>();
        private Map<String, ExchangeRateTimeSeries> predecessors = new HashMap<>();
        private Map<String, Integer> distance = new HashMap<>();

        public Dijkstra(List<ExchangeRateTimeSeries> availableTimeSeries, String baseCurrency)
        {
            availableTimeSeries.stream().forEach(ts -> {
                timeSeries.add(ts);
                timeSeries.add(new InverseExchangeRateTimeSeries(ts));
            });

            distance.put(baseCurrency, 0);

            unvisited = CurrencyUnit.getAvailableCurrencyUnits().stream().map(CurrencyUnit::getCurrencyCode)
                            .collect(Collectors.toSet());

            if (!unvisited.contains(baseCurrency))
                return;

            while (!unvisited.isEmpty())
            {
                String node = getNodeWithMinimumDistance(unvisited);
                unvisited.remove(node);
                visited.add(node);

                for (ExchangeRateTimeSeries neighbor : getNeighbors(node))
                {
                    String neighborNode = neighbor.getTermCurrency();
                    int alternativeDistance = getDistance(node) + neighbor.getWeight();

                    if (alternativeDistance < getDistance(neighborNode))
                    {
                        distance.put(neighborNode, alternativeDistance);
                        predecessors.put(neighborNode, neighbor);
                    }
                }
            }
        }

        public List<ExchangeRateTimeSeries> findShortestPath(String termCurrency)
        {
            ExchangeRateTimeSeries current = predecessors.get(termCurrency);
            if (current == null)
                return Collections.emptyList();

            LinkedList<ExchangeRateTimeSeries> answer = new LinkedList<>();
            while (current != null)
            {
                answer.addFirst(current);
                current = predecessors.get(current.getBaseCurrency());
            }

            return answer;
        }

        private List<ExchangeRateTimeSeries> getNeighbors(String node)
        {
            return timeSeries.stream()
                            .filter(ts -> ts.getBaseCurrency().equals(node) && !visited.contains(ts.getTermCurrency()))
                            .collect(Collectors.toList());
        }

        private String getNodeWithMinimumDistance(Set<String> currencies)
        {
            return currencies.stream().min(Comparator.comparingInt(this::getDistance))
                            .orElse(currencies.iterator().next());
        }

        private int getDistance(String currency)
        {
            Integer d = distance.get(currency);
            return d == null ? Integer.MAX_VALUE : d;
        }
    }

    private static class CurrencyPair
    {
        private final String base;
        private final String term;

        public CurrencyPair(String base, String term)
        {
            this.base = base;
            this.term = term;
        }

        @Override
        public int hashCode()
        {
            return Objects.hash(base, term);
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;

            CurrencyPair other = (CurrencyPair) obj;

            if (!Objects.equals(base, other.base))
                return false;

            return Objects.equals(term, other.term);
        }

    }

    private final List<ExchangeRateProvider> providers;
    private final ConcurrentMap<CurrencyPair, ExchangeRateTimeSeries> cache = new ConcurrentHashMap<>();

    public ExchangeRateProviderFactory()
    {
        providers = new ArrayList<>();
        Iterator<ExchangeRateProvider> registeredProvider = ServiceLoader.load(ExchangeRateProvider.class).iterator();
        while (registeredProvider.hasNext())
        {
            ExchangeRateProvider provider = registeredProvider.next();
            providers.add(provider);
        }
    }

    public List<ExchangeRateProvider> getProviders()
    {
        return Collections.unmodifiableList(providers);
    }

    public List<ExchangeRateTimeSeries> getAvailableTimeSeries()
    {
        List<ExchangeRateTimeSeries> series = new ArrayList<>();
        for (ExchangeRateProvider p : providers)
            series.addAll(p.getAvailableTimeSeries());
        return series;
    }

{'fraction_non_alphanumeric': 0.09296004349007883, 'fraction_numerical': 0.0003624173235480656, 'mean_word_length': 2.0996910980061783, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 57, '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': '3025257', 'n_tokens_mistral': 2785, 'n_tokens_neox': 2662, 'n_words': 546}