aboutsummaryrefslogtreecommitdiff
path: root/s2storage/src/write/java/com/android/timezone/location/storage/tzs2range/write/BankedTzIdSetsPacker.java
blob: e4a76660b72dcb354dcf246d8ce3687a0d9b1f01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 com.android.timezone.location.storage.tzs2range.write;

import com.android.timezone.location.storage.tzs2range.BankedTzIdSets;
import com.android.timezone.location.storage.tzs2range.TzS2Range;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A simple generator of {@link BankedTzIdSets}.
 */
public final class BankedTzIdSetsPacker {

    private final Map<String, Integer> mStringToStringId = new HashMap<>();

    private final List<String> mStringIdToString = new ArrayList<>();

    private final List<BankHelper> mBankHelpers = new ArrayList<>();

    private final int mMaxBankSize;

    private int mNextBankId = 0;

    private int mNextStringId = 0;

    /**
     * Creates a {@link BankedTzIdSetsPacker} that keeps banks to {@code maxBankSize} or smaller.
     */
    public BankedTzIdSetsPacker(int maxBankSize) {
        mMaxBankSize = maxBankSize;
    }

    /**
     * Adds multiple TZ ID sets for a chunk of time zone geo data. The sets are already expected to
     * be unique. The sets are ordered so differently ordered sets containing the same strings will
     * be kept distinct. The packer will record any hitherto unknown IDs and attempt to pack the
     * sets into an existing bank or will create a new one. The {@link BankHelper} for the bank
     * found / created will be returned.
     *
     * <p>If there are too many sets or too many banks, an {@link IllegalArgumentException} will be
     * thrown.
     */
    public BankHelper addTzIdSets(List<List<String>> uniqueIdSets) {
        if (uniqueIdSets.size() < 1 || uniqueIdSets.size() > mMaxBankSize) {
            throw new IllegalArgumentException(
                    "uniqueIdSets.size()=" + uniqueIdSets.size() + " < 1 or > " + mMaxBankSize);
        }

        // Add new strings to the string pool.
        for (List<String> stringSets : uniqueIdSets) {
            for (String string : stringSets) {
                if (!mStringToStringId.containsKey(string)) {
                    mStringToStringId.put(string, mNextStringId++);
                    mStringIdToString.add(string);
                }
            }
        }

        // Packing could be better, but it doesn't need to be optimal.
        for (BankHelper bankHelper : mBankHelpers) {
            if (bankHelper.offer(uniqueIdSets)) {
                return bankHelper;
            }
        }

        // None of the existing banks can accept the sets.
        BankHelper bankHelper = new BankHelper(mNextBankId++);
        mBankHelpers.add(bankHelper);
        if (!bankHelper.offer(uniqueIdSets)) {
            // A new bank should be able to accept the sets.
            throw new IllegalStateException(
                    "uniqueIdSets=" + uniqueIdSets + " cannot be stored in a new bank");
        }
        return bankHelper;
    }

    /**
     * Creates the {@link BankedTzIdSets}.
     */
    public BankedTzIdSets create() {
        BankedTzIdSets.Builder builder = new BankedTzIdSets.Builder();
        builder.setStringsByIndex(mStringIdToString);
        for (BankHelper bankHelper : mBankHelpers) {
            BankedTzIdSets.Builder.BankBuilder bank = builder.newBank();
            List<List<Integer>> tzIdSets = bankHelper.getTzIdSets();
            for (List<Integer> tzIdSet : tzIdSets) {
                bank.addTzIdSet(tzIdSet);
            }
        }
        return builder.build();
    }

    /**
     * Information about a Bank that will be used to store the TZ ID sets passed to
     * {@link BankedTzIdSetsPacker#addTzIdSets(List)}. It can be used to obtain the bank's ID and
     * the set ID for a given TZ ID set.
     */
    public final class BankHelper {

        private final int mId;

        private final Map<List<Integer>, Integer> mSetToSetId = new HashMap<>();

        private final List<List<Integer>> mSetIdToSet = new ArrayList<>();

        private int mNextSetId = 0;

        BankHelper(int id) {
            mId = id;
        }

        public int getId() {
            return mId;
        }

        private void addAll(List<List<String>> uniqueTzIdStringSets) {
            for (List<String> uniqueTzIdStringIdSet : uniqueTzIdStringSets) {
                List<Integer> uniqueTzIdIntSet = getStringIdList(uniqueTzIdStringIdSet);

                if (mSetToSetId.containsKey(uniqueTzIdIntSet)) {
                    throw new IllegalArgumentException("setToSetId=" + mSetIdToSet
                            + " already contains uniqueTzIdIntSet=" + uniqueTzIdIntSet);
                }
                mSetToSetId.put(uniqueTzIdIntSet, mNextSetId++);
                mSetIdToSet.add(uniqueTzIdIntSet);
                if (mNextSetId > mMaxBankSize) {
                    throw new IllegalStateException("nextSetId=" + mNextSetId
                            + " exceeds maxBankSize=" + mMaxBankSize);
                }
            }
        }

        private List<Integer> getStringIdList(List<String> uniqueTzIdStringIdSet) {
            if (uniqueTzIdStringIdSet.size() < 1) {
                throw new IllegalArgumentException("uniqueTzIdStringIdSet.size() must >= 1");
            }
            List<Integer> uniqueTzIdIntSet = new ArrayList<>(uniqueTzIdStringIdSet.size());
            for (String string : uniqueTzIdStringIdSet) {
                Integer stringId = mStringToStringId.get(string);
                if (stringId == null) {
                    throw new IllegalStateException("Unable to find index for string=" + string);
                }
                uniqueTzIdIntSet.add(stringId);
            }
            return uniqueTzIdIntSet;
        }

        int getTzIdSetId(List<String> tzIdStringSet) {
            List<Integer> tzIdIntSet = getStringIdList(tzIdStringSet);
            Integer setId = mSetToSetId.get(tzIdIntSet);
            if (setId == null) {
                return -1;
            }
            return setId;
        }

        List<List<Integer>> getTzIdSets() {
            return Collections.unmodifiableList(new ArrayList<>(mSetIdToSet));
        }

        boolean offer(List<List<String>> uniqueIdSets) {
            List<List<String>> newSets = new ArrayList<>();
            for (List<String> uniqueIdSet : uniqueIdSets) {
                List<Integer> uniqueIdSetIntegers = getStringIdList(uniqueIdSet);
                if (!mSetToSetId.containsKey(uniqueIdSetIntegers)) {
                    newSets.add(uniqueIdSet);
                }
            }
            if (mSetToSetId.size() + newSets.size() > mMaxBankSize) {
                return false;
            }
            addAll(newSets);
            return true;
        }
    }

    /**
     * Obtains the unique TZ ID sets from a list of {@link TzS2Range} files. TZ ID sets are
     * considered ordered so two sets containing the same IDs in different orders are considered
     * distinct.
     */
    public static List<List<String>> extractUniqueTzIdSets(List<TzS2Range> ranges) {
        Set<List<String>> sets = new HashSet<>();
        for (TzS2Range range : ranges) {
            sets.add(range.getTzIdSet());
        }
        return new ArrayList<>(sets);
    }
}