001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.common.string2long;
019
020import static com.google.common.collect.ImmutableList.copyOf;
021
022import com.google.common.collect.ImmutableList;
023import com.google.common.collect.ImmutableMap;
024import com.google.errorprone.annotations.Immutable;
025
026import java.util.HashMap;
027import java.util.Map;
028
029/**
030 * Immutable implementation of {@link StringToLongBiMap}.
031 *
032 * <p>The Builder of this class is NOT thread-safe, but the resulting built instance is (and it's
033 * FASTER; e.g. faster than the {@link ConcurrentStringToLongBiMap}).
034 *
035 * <p>As currently implemented, this only actually supports up to {@link Integer#MAX_VALUE} (NOT
036 * Long!) number of symbols. The API, however, does allow for future expansion of this
037 * implementation to and other implementations which support more symbols.
038 */
039@Immutable
040public class ImmutableStringToLongBiMap implements StringToLongBiMap {
041
042    private final ImmutableMap<String, Integer> symbolsMap;
043    private final ImmutableList<String> symbols;
044
045    private ImmutableStringToLongBiMap(
046            ImmutableMap<String, Integer> symbolsMap, ImmutableList<String> symbols) {
047        this.symbolsMap = symbolsMap;
048        this.symbols = symbols;
049    }
050
051    @Override // ~copy/paste from ConcurrentStringToLongBiMap
052    public void get(String symbol, LongOrStringConsumer consumer) {
053        var id = symbolsMap.get(symbol);
054        if (id != null) consumer.longID(id);
055        else consumer.string(symbol);
056    }
057
058    @Override
059    public String get(long id) throws IllegalArgumentException {
060        if (id >= 0 && id < symbols.size()) return symbols.get((int) id);
061        else throw new IllegalArgumentException(Long.toUnsignedString(id));
062    }
063
064    @Override
065    public long size() {
066        return symbols.size();
067    }
068
069    @Override
070    public Iterable<String> symbols() {
071        return symbols;
072    }
073
074    public static Builder builder() {
075        return new BuilderImpl();
076    }
077
078    private static class BuilderImpl implements Builder {
079
080        // TODO Is there EVER going to be any need to support long instead of int size? Nota bene:
081        // ConcurrentStringToLongBiMap already supports up to [#MAX_VALUE] (not just Int) number of
082        // symbols.
083
084        private final Map<String, Integer> map = new HashMap<>();
085        private int nextId = 0;
086
087        @Override
088        public long put(String symbol) {
089            if (nextId == Integer.MAX_VALUE) throw new IllegalStateException();
090            var id = map.get(symbol);
091            if (id != null) return id;
092            else {
093                map.put(symbol, nextId);
094                return nextId++;
095            }
096        }
097
098        @Override
099        public StringToLongBiMap build() {
100            var size = map.size();
101            var immutableMapBuilder = ImmutableMap.<String, Integer>builderWithExpectedSize(size);
102            var array = new String[size];
103            map.forEach(
104                    (symbol, id) -> {
105                        immutableMapBuilder.put(symbol, id);
106                        if (array[id] != null) throw new IllegalStateException();
107                        array[id] = symbol;
108                    });
109            return new ImmutableStringToLongBiMap(immutableMapBuilder.build(), copyOf(array));
110        }
111    }
112}