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}