• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.timezone.location.storage.tzs2range.write;
18 
19 import static com.android.timezone.location.storage.s2.S2Support.cellIdToString;
20 
21 import com.android.timezone.location.storage.block.read.BlockData;
22 import com.android.timezone.location.storage.block.write.BlockWriter;
23 import com.android.timezone.location.storage.block.write.EmptyBlockWriter;
24 import com.android.timezone.location.storage.io.write.TypedOutputStream;
25 import com.android.timezone.location.storage.s2.S2Support;
26 import com.android.timezone.location.storage.table.packed.write.PackedTableWriter;
27 import com.android.timezone.location.storage.tzs2range.SuffixTableRange;
28 import com.android.timezone.location.storage.tzs2range.SuffixTableSharedData;
29 import com.android.timezone.location.storage.tzs2range.TzS2RangeFileFormat;
30 import com.android.timezone.location.storage.tzs2range.read.SuffixTableExtraInfo;
31 
32 import java.io.ByteArrayOutputStream;
33 import java.io.File;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.nio.MappedByteBuffer;
37 import java.nio.channels.FileChannel;
38 import java.nio.file.StandardOpenOption;
39 
40 /**
41  * A class used to generate suffix tables block info and block data.
42  * To write empty tables use {@link #createEmptyBlockWriter()}.
43  * To write populated tables use {@link
44  * #createPopulated(TzS2RangeFileFormat, SuffixTableSharedData)} and add entries with
45  * {@link #addRange(SuffixTableRange)}
46  */
47 public final class SuffixTableWriter implements BlockWriter {
48 
49     private final SuffixTableSharedData mSharedData;
50 
51     private final TzS2RangeFileFormat mFileFormat;
52 
53     private final PackedTableWriter mPackedTableWriter;
54 
55     private final File mFile;
56 
57     private SuffixTableRange mLastRangeAdded;
58 
SuffixTableWriter(TzS2RangeFileFormat fileFormat, SuffixTableSharedData sharedData)59     private SuffixTableWriter(TzS2RangeFileFormat fileFormat, SuffixTableSharedData sharedData)
60             throws IOException {
61         mFileFormat = fileFormat;
62         mSharedData = sharedData;
63 
64         int keySizeBits = fileFormat.getSuffixBitCount();
65         int entrySizeByteCount = fileFormat.getTableEntryByteCount();
66         mFile = File.createTempFile("suffixtablewriter", ".packed");
67 
68         byte[] blockSharedData = SuffixTableSharedDataWriter.toBytes(sharedData);
69         FileOutputStream fileOutputStream = new FileOutputStream(mFile);
70         boolean signedValue = false;
71         mPackedTableWriter = PackedTableWriter.create(
72                 fileOutputStream, entrySizeByteCount, keySizeBits, signedValue, blockSharedData);
73     }
74 
75     /** Returns a {@link BlockWriter} capable of generating the block data for an empty table. */
createEmptyBlockWriter()76     public static BlockWriter createEmptyBlockWriter() {
77         return new EmptyBlockWriter(TzS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE);
78     }
79 
80     /** Returns a {@link BlockWriter} capable of generating the block data for a populated table. */
createPopulated( TzS2RangeFileFormat fileFormat, SuffixTableSharedData sharedData)81     public static SuffixTableWriter createPopulated(
82             TzS2RangeFileFormat fileFormat, SuffixTableSharedData sharedData) throws IOException {
83         return new SuffixTableWriter(fileFormat, sharedData);
84     }
85 
86     /**
87      * Adds the supplied range to the table. The range must start after any previously added range,
88      * no overlap is allowed. Gaps are permitted. The range must have the expected S2 cell ID
89      * prefix. Invalid ranges will cause {@link IllegalArgumentException}. This method must be
90      * called at least once. See {@link SuffixTableWriter#createEmptyBlockWriter()} for empty
91      * tables.
92      */
addRange(SuffixTableRange suffixTableRange)93     public void addRange(SuffixTableRange suffixTableRange) throws IOException {
94         checkIsOpen();
95 
96         long rangeStartCellId = suffixTableRange.getStartCellId();
97         long rangeEndCellId = suffixTableRange.getEndCellId();
98 
99         // Check range belongs in this table.
100         int rangeStartPrefixValue = mFileFormat.extractPrefixValueFromCellId(rangeStartCellId);
101         int rangeStartSuffixValue = mFileFormat.extractSuffixValueFromCellId(rangeStartCellId);
102         if (rangeStartPrefixValue != mSharedData.getTablePrefix()) {
103             throw new IllegalArgumentException(
104                     "rangeStartCellId=" + cellIdToString(rangeStartCellId)
105                             + " has a different prefix=" + rangeStartPrefixValue
106                             + " than the table prefix=" + mSharedData.getTablePrefix());
107         }
108 
109         long rangeEndCellIdInclusive = S2Support.offsetCellId(rangeEndCellId, -1);
110         int rangeEndPrefixValue = mFileFormat.extractPrefixValueFromCellId(rangeEndCellIdInclusive);
111         if (rangeEndPrefixValue != rangeStartPrefixValue) {
112             // Because SuffixTableRange has an exclusive end value, rangeEndPrefixValue is allowed
113             // to be the next prefix value if the rangeEndSuffixValue == 0.
114             int rangeEndSuffixValue = mFileFormat.extractSuffixValueFromCellId(rangeEndCellId);
115             if (!(rangeEndPrefixValue == rangeStartPrefixValue + 1 && rangeEndSuffixValue == 0)) {
116                 throw new IllegalArgumentException("rangeEndPrefixValue=" + rangeEndPrefixValue
117                         + " != rangeStartPrefixValue=" + rangeStartPrefixValue);
118             }
119         }
120 
121         // Confirm the new range starts after the end of the last one that was added, if any.
122         if (mLastRangeAdded != null) {
123             long lastRangeAddedEndCellId = mLastRangeAdded.getEndCellId();
124             int lastRangeEndPrefixValue =
125                     mFileFormat.extractPrefixValueFromCellId(lastRangeAddedEndCellId);
126             if (lastRangeEndPrefixValue != mSharedData.getTablePrefix()) {
127                 // Deal with the special case where the last range added completed the table.
128                 throw new IllegalArgumentException(
129                         "Suffix table is full: last range added=" + mLastRangeAdded);
130             } else {
131                 int lastRangeEndSuffixValue =
132                         mFileFormat.extractSuffixValueFromCellId(lastRangeAddedEndCellId);
133                 if (rangeStartSuffixValue < lastRangeEndSuffixValue) {
134                     throw new IllegalArgumentException("suffixTableRange=" + suffixTableRange
135                             + " overlaps with last range added=" + mLastRangeAdded);
136                 }
137             }
138         }
139 
140         int rangeLength = mFileFormat.calculateRangeLength(rangeStartCellId, rangeEndCellId);
141 
142         int tzIdSetId = suffixTableRange.getTzIdSetId();
143         if (tzIdSetId < 0 || tzIdSetId > mFileFormat.getMaxTzIdSetIdValue()) {
144             throw new IllegalArgumentException("suffixTableRange.getTzSetId()=" + tzIdSetId
145                     + " is outside of the allowed range."
146                     + " Max value=" + mFileFormat.getMaxTzIdSetIdValue());
147         }
148 
149         long value = mFileFormat.createSuffixTableValue(rangeLength, tzIdSetId);
150         mPackedTableWriter.addEntry(rangeStartSuffixValue, value);
151         mLastRangeAdded = suffixTableRange;
152     }
153 
154     @Override
close()155     public ReadBack close() throws IOException {
156         checkIsOpen();
157         mPackedTableWriter.close();
158         mLastRangeAdded = null;
159 
160         int entryCount = mPackedTableWriter.getEntryCount();
161         if (entryCount == 0) {
162             throw new IllegalStateException("No ranges added. For an empty suffix table, use"
163                     + " createEmptySuffixTableBlockWriter()");
164         }
165 
166         FileChannel fileChannel = FileChannel.open(mFile.toPath(), StandardOpenOption.READ);
167         MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, mFile.length());
168         fileChannel.close();
169 
170         // Writes the number of entries into the extra bytes stored in the BlockInfo. This means the
171         // number of entries can be known without reading the block data at all.
172         SuffixTableExtraInfo suffixTableExtraInfo =
173                 new SuffixTableExtraInfo(mSharedData.getTablePrefix(), entryCount);
174         byte[] blockInfoExtraBytes = generateBlockInfoExtraBytes(suffixTableExtraInfo);
175         BlockData blockData = new BlockData(map);
176         return new ReadBack() {
177             @Override
178             public byte[] getExtraBytes() {
179                 return blockInfoExtraBytes;
180             }
181 
182             @Override
183             public int getType() {
184                 return TzS2RangeFileFormat.BLOCK_TYPE_SUFFIX_TABLE;
185             }
186 
187             @Override
188             public BlockData getBlockData() {
189                 return blockData;
190             }
191         };
192     }
193 
194     private void checkIsOpen() {
195         if (!mPackedTableWriter.isOpen()) {
196             throw new IllegalStateException("Writer is closed.");
197         }
198     }
199 
200     private static byte[] generateBlockInfoExtraBytes(SuffixTableExtraInfo suffixTableBlockInfo) {
201         ByteArrayOutputStream baos = new ByteArrayOutputStream();
202         try (TypedOutputStream tos = new TypedOutputStream(baos)) {
203             tos.writeInt(suffixTableBlockInfo.getEntryCount());
204         } catch (IOException e) {
205             throw new IllegalStateException("Unexpected IOException writing to byte array", e);
206         }
207         return baos.toByteArray();
208     }
209 }
210