1 package org.apache.lucene.index;
2
3 /**
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 import org.apache.lucene.store.Directory;
21 import org.apache.lucene.store.IndexInput;
22 import org.apache.lucene.store.BufferedIndexInput;
23 import org.apache.lucene.store.IndexOutput;
24 import org.apache.lucene.store.Lock;
25
26 import java.util.HashMap;
27 import java.io.IOException;
28
29
30 /**
31 * Class for accessing a compound stream.
32 * This class implements a directory, but is limited to only read operations.
33 * Directory methods that would normally modify data throw an exception.
34 */
35 class CompoundFileReader extends Directory {
36
37 private int readBufferSize;
38
39 private static final class FileEntry {
40 long offset;
41 long length;
42 }
43
44
45 // Base info
46 private Directory directory;
47 private String fileName;
48
49 private IndexInput stream;
50 private HashMap<String,FileEntry> entries = new HashMap<String,FileEntry>();
51
52
53 public CompoundFileReader(Directory dir, String name) throws IOException {
54 this(dir, name, BufferedIndexInput.BUFFER_SIZE);
55 }
56
57 public CompoundFileReader(Directory dir, String name, int readBufferSize)
58 throws IOException
59 {
60 directory = dir;
61 fileName = name;
62 this.readBufferSize = readBufferSize;
63
64 boolean success = false;
65
66 try {
67 stream = dir.openInput(name, readBufferSize);
68
69 // read the directory and init files
70 int count = stream.readVInt();
71 FileEntry entry = null;
72 for (int i=0; i<count; i++) {
73 long offset = stream.readLong();
74 String id = stream.readString();
75
76 if (entry != null) {
77 // set length of the previous entry
78 entry.length = offset - entry.offset;
79 }
80
81 entry = new FileEntry();
82 entry.offset = offset;
83 entries.put(id, entry);
84 }
85
86 // set the length of the final entry
87 if (entry != null) {
88 entry.length = stream.length() - entry.offset;
89 }
90
91 success = true;
92
93 } finally {
94 if (! success && (stream != null)) {
95 try {
96 stream.close();
97 } catch (IOException e) { }
98 }
99 }
100 }
101
102 public Directory getDirectory() {
103 return directory;
104 }
105
106 public String getName() {
107 return fileName;
108 }
109
110 @Override
111 public synchronized void close() throws IOException {
112 if (stream == null)
113 throw new IOException("Already closed");
114
115 entries.clear();
116 stream.close();
117 stream = null;
118 }
119
120 @Override
121 public synchronized IndexInput openInput(String id)
122 throws IOException
123 {
124 // Default to readBufferSize passed in when we were opened
125 return openInput(id, readBufferSize);
126 }
127
128 @Override
129 public synchronized IndexInput openInput(String id, int readBufferSize)
130 throws IOException
131 {
132 if (stream == null)
133 throw new IOException("Stream closed");
134
135 FileEntry entry = entries.get(id);
136 if (entry == null)
137 throw new IOException("No sub-file with id " + id + " found");
138
139 return new CSIndexInput(stream, entry.offset, entry.length, readBufferSize);
140 }
141
142 /** Returns an array of strings, one for each file in the directory. */
143 @Override
144 public String[] listAll() {
145 String res[] = new String[entries.size()];
146 return entries.keySet().toArray(res);
147 }
148
149 /** Returns true iff a file with the given name exists. */
150 @Override
151 public boolean fileExists(String name) {
152 return entries.containsKey(name);
153 }
154
155 /** Returns the time the compound file was last modified. */
156 @Override
157 public long fileModified(String name) throws IOException {
158 return directory.fileModified(fileName);
159 }
160
161 /** Set the modified time of the compound file to now. */
162 @Override
163 public void touchFile(String name) throws IOException {
164 directory.touchFile(fileName);
165 }
166
167 /** Not implemented
168 * @throws UnsupportedOperationException */
169 @Override
170 public void deleteFile(String name)
171 {
172 throw new UnsupportedOperationException();
173 }
174
175 /** Not implemented
176 * @throws UnsupportedOperationException */
177 public void renameFile(String from, String to)
178 {
179 throw new UnsupportedOperationException();
180 }
181
182 /** Returns the length of a file in the directory.
183 * @throws IOException if the file does not exist */
184 @Override
185 public long fileLength(String name)
186 throws IOException
187 {
188 FileEntry e = entries.get(name);
189 if (e == null)
190 throw new IOException("File " + name + " does not exist");
191 return e.length;
192 }
193
194 /** Not implemented
195 * @throws UnsupportedOperationException */
196 @Override
197 public IndexOutput createOutput(String name)
198 {
199 throw new UnsupportedOperationException();
200 }
201
202 /** Not implemented
203 * @throws UnsupportedOperationException */
204 @Override
205 public Lock makeLock(String name)
206 {
207 throw new UnsupportedOperationException();
208 }
209
210 /** Implementation of an IndexInput that reads from a portion of the
211 * compound file. The visibility is left as "package" *only* because
212 * this helps with testing since JUnit test cases in a different class
213 * can then access package fields of this class.
214 */
215 static final class CSIndexInput extends BufferedIndexInput {
216
217 IndexInput base;
218 long fileOffset;
219 long length;
220
221 CSIndexInput(final IndexInput base, final long fileOffset, final long length)
222 {
223 this(base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE);
224 }
225
226 CSIndexInput(final IndexInput base, final long fileOffset, final long length, int readBufferSize)
227 {
228 super(readBufferSize);
229 this.base = (IndexInput)base.clone();
230 this.fileOffset = fileOffset;
231 this.length = length;
232 }
233
234 @Override
235 public Object clone() {
236 CSIndexInput clone = (CSIndexInput)super.clone();
237 clone.base = (IndexInput)base.clone();
238 clone.fileOffset = fileOffset;
239 clone.length = length;
240 return clone;
241 }
242
243 /** Expert: implements buffer refill. Reads bytes from the current
244 * position in the input.
245 * @param b the array to read bytes into
246 * @param offset the offset in the array to start storing bytes
247 * @param len the number of bytes to read
248 */
249 @Override
250 protected void readInternal(byte[] b, int offset, int len)
251 throws IOException
252 {
253 long start = getFilePointer();
254 if(start + len > length)
255 throw new IOException("read past EOF");
256 base.seek(fileOffset + start);
257 base.readBytes(b, offset, len, false);
258 }
259
260 /** Expert: implements seek. Sets current position in this file, where
261 * the next {@link #readInternal(byte[],int,int)} will occur.
262 * @see #readInternal(byte[],int,int)
263 */
264 @Override
265 protected void seekInternal(long pos) {}
266
267 /** Closes the stream to further operations. */
268 @Override
269 public void close() throws IOException {
270 base.close();
271 }
272
273 @Override
274 public long length() {
275 return length;
276 }
277
278
279 }
280
281 }