1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.index.reader;
20
21 import java.io.Closeable;
22 import java.io.DataInput;
23 import java.io.DataInputStream;
24 import java.io.EOFException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.UTFDataFormatException;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.NoSuchElementException;
33 import java.util.zip.GZIPInputStream;
34
35
36
37
38
39
40
41
42 public class ChunkReader implements Closeable, Iterable<Map<String, String>> {
43 private final String chunkName;
44
45 private final DataInputStream dataInputStream;
46
47 private final int version;
48
49 private final Date timestamp;
50
51 public ChunkReader(final String chunkName, final InputStream inputStream) throws IOException {
52 this.chunkName = chunkName.trim();
53 this.dataInputStream = new DataInputStream(new GZIPInputStream(inputStream, 2 * 1024));
54 this.version = ((int) dataInputStream.readByte()) & 0xff;
55 this.timestamp = new Date(dataInputStream.readLong());
56 }
57
58
59
60
61 public String getName() {
62 return chunkName;
63 }
64
65
66
67
68 public int getVersion() {
69 return version;
70 }
71
72
73
74
75 public Date getTimestamp() {
76 return timestamp;
77 }
78
79
80
81
82 @Override
83 public Iterator<Map<String, String>> iterator() {
84 try {
85 return new IndexIterator(dataInputStream);
86 } catch (IOException e) {
87 throw new RuntimeException("error", e);
88 }
89 }
90
91
92
93
94 @Override
95 public void close() throws IOException {
96 dataInputStream.close();
97 }
98
99
100
101
102 private static class IndexIterator implements Iterator<Map<String, String>> {
103 private final DataInputStream dataInputStream;
104
105 private Map<String, String> nextRecord;
106
107 private IndexIterator(final DataInputStream dataInputStream) throws IOException {
108 this.dataInputStream = dataInputStream;
109 this.nextRecord = nextRecord();
110 }
111
112 @Override
113 public boolean hasNext() {
114 return nextRecord != null;
115 }
116
117 @Override
118 public Map<String, String> next() {
119 if (nextRecord == null) {
120 throw new NoSuchElementException("chunk depleted");
121 }
122 Map<String, String> result = nextRecord;
123 nextRecord = nextRecord();
124 return result;
125 }
126
127 @Override
128 public void remove() {
129 throw new UnsupportedOperationException("remove");
130 }
131
132 private Map<String, String> nextRecord() {
133 try {
134 return readRecord(dataInputStream);
135 } catch (IOException e) {
136 throw new RuntimeException("read error", e);
137 }
138 }
139 }
140
141
142
143
144 private static Map<String, String> readRecord(final DataInput dataInput) throws IOException {
145 int fieldCount;
146 try {
147 fieldCount = dataInput.readInt();
148 } catch (EOFException ex) {
149 return null;
150 }
151
152 Map<String, String> recordMap = new HashMap<>();
153 for (int i = 0; i < fieldCount; i++) {
154 readField(recordMap, dataInput);
155 }
156 return recordMap;
157 }
158
159 private static void readField(final Map<String, String> record, final DataInput dataInput) throws IOException {
160 dataInput.readByte();
161 String name = dataInput.readUTF();
162 String value = readUTF(dataInput);
163 record.put(name, value);
164 }
165
166 private static String readUTF(final DataInput dataInput) throws IOException {
167 int utflen = dataInput.readInt();
168
169 byte[] bytearr;
170 char[] chararr;
171
172 try {
173 bytearr = new byte[utflen];
174 chararr = new char[utflen];
175 } catch (OutOfMemoryError e) {
176 throw new IOException("Index data content is corrupt", e);
177 }
178
179 int c, char2, char3;
180 int count = 0;
181 int chararrCount = 0;
182
183 dataInput.readFully(bytearr, 0, utflen);
184
185 while (count < utflen) {
186 c = bytearr[count] & 0xff;
187 if (c > 127) {
188 break;
189 }
190 count++;
191 chararr[chararrCount++] = (char) c;
192 }
193
194 while (count < utflen) {
195 c = bytearr[count] & 0xff;
196 switch (c >> 4) {
197 case 0:
198 case 1:
199 case 2:
200 case 3:
201 case 4:
202 case 5:
203 case 6:
204 case 7:
205
206 count++;
207 chararr[chararrCount++] = (char) c;
208 break;
209
210 case 12:
211 case 13:
212
213 count += 2;
214 if (count > utflen) {
215 throw new UTFDataFormatException("malformed input: partial character at end");
216 }
217 char2 = bytearr[count - 1];
218 if ((char2 & 0xC0) != 0x80) {
219 throw new UTFDataFormatException("malformed input around byte " + count);
220 }
221 chararr[chararrCount++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
222 break;
223
224 case 14:
225
226 count += 3;
227 if (count > utflen) {
228 throw new UTFDataFormatException("malformed input: partial character at end");
229 }
230 char2 = bytearr[count - 2];
231 char3 = bytearr[count - 1];
232 if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) {
233 throw new UTFDataFormatException("malformed input around byte " + (count - 1));
234 }
235 chararr[chararrCount++] = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F));
236 break;
237
238 default:
239
240 throw new UTFDataFormatException("malformed input around byte " + count);
241 }
242 }
243
244
245 return new String(chararr, 0, chararrCount);
246 }
247 }