001 package gate.creole.annic.apache.lucene.store;
002
003 /**
004 * Copyright 2004 The Apache Software Foundation
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 * http://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 */
018
019 import java.io.IOException;
020 import java.io.File;
021 import java.io.RandomAccessFile;
022 import java.io.FileInputStream;
023 import java.io.FileOutputStream;
024 import java.util.Hashtable;
025 import java.security.MessageDigest;
026 import java.security.NoSuchAlgorithmException;
027
028 import gate.creole.annic.apache.lucene.util.Constants;
029
030 /**
031 * Straightforward implementation of {@link Directory} as a directory of files.
032 * <p>If the system property 'disableLuceneLocks' has the String value of
033 * "true", lock creation will be disabled.
034 *
035 * @see Directory
036 * @author Doug Cutting
037 */
038 public final class FSDirectory extends Directory {
039 /** This cache of directories ensures that there is a unique Directory
040 * instance per path, so that synchronization on the Directory can be used to
041 * synchronize access between readers and writers.
042 *
043 * This should be a WeakHashMap, so that entries can be GC'd, but that would
044 * require Java 1.2. Instead we use refcounts...
045 */
046 private static final Hashtable DIRECTORIES = new Hashtable();
047
048 private static final boolean DISABLE_LOCKS =
049 Boolean.getBoolean("disableLuceneLocks") || Constants.JAVA_1_1;
050
051 /**
052 * Directory specified by <code>gate.creole.annic.apache.lucene.lockdir</code>
053 * or <code>java.io.tmpdir</code> system property
054 */
055 public static final String LOCK_DIR =
056 System.getProperty("gate.creole.annic.apache.lucene.lockdir",
057 System.getProperty("java.io.tmpdir"));
058
059 private static MessageDigest DIGESTER;
060
061 static {
062 try {
063 DIGESTER = MessageDigest.getInstance("MD5");
064 } catch (NoSuchAlgorithmException e) {
065 throw new RuntimeException(e.toString());
066 }
067 }
068
069 /** A buffer optionally used in renameTo method */
070 private byte[] buffer = null;
071
072 /** Returns the directory instance for the named location.
073 *
074 * <p>Directories are cached, so that, for a given canonical path, the same
075 * FSDirectory instance will always be returned. This permits
076 * synchronization on directories.
077 *
078 * @param path the path to the directory.
079 * @param create if true, create, or erase any existing contents.
080 * @return the FSDirectory for the named file. */
081 public static FSDirectory getDirectory(String path, boolean create)
082 throws IOException {
083 return getDirectory(new File(path), create);
084 }
085
086 /** Returns the directory instance for the named location.
087 *
088 * <p>Directories are cached, so that, for a given canonical path, the same
089 * FSDirectory instance will always be returned. This permits
090 * synchronization on directories.
091 *
092 * @param file the path to the directory.
093 * @param create if true, create, or erase any existing contents.
094 * @return the FSDirectory for the named file. */
095 public static FSDirectory getDirectory(File file, boolean create)
096 throws IOException {
097 file = new File(file.getCanonicalPath());
098 FSDirectory dir;
099 synchronized (DIRECTORIES) {
100 dir = (FSDirectory)DIRECTORIES.get(file);
101 if (dir == null) {
102 dir = new FSDirectory(file, create);
103 DIRECTORIES.put(file, dir);
104 } else if (create) {
105 dir.create();
106 }
107 }
108 synchronized (dir) {
109 dir.refCount++;
110 }
111 return dir;
112 }
113
114 private File directory = null;
115 private int refCount;
116 private File lockDir;
117
118 private FSDirectory(File path, boolean create) throws IOException {
119 directory = path;
120
121 if (LOCK_DIR == null) {
122 lockDir = directory;
123 }
124 else {
125 lockDir = new File(LOCK_DIR);
126 }
127 if (create) {
128 create();
129 }
130
131 if (!directory.isDirectory())
132 throw new IOException(path + " not a directory");
133 }
134
135 private synchronized void create() throws IOException {
136 if (!directory.exists())
137 if (!directory.mkdirs())
138 throw new IOException("Cannot create directory: " + directory);
139
140 String[] files = directory.list(); // clear old files
141 for (int i = 0; i < files.length; i++) {
142 File file = new File(directory, files[i]);
143 if(!file.isDirectory()) {
144 if (!file.delete())
145 throw new IOException("Cannot delete " + files[i]);
146 }
147 }
148
149 String lockPrefix = getLockPrefix().toString(); // clear old locks
150 files = lockDir.list();
151 for (int i = 0; i < files.length; i++) {
152 if (!files[i].startsWith(lockPrefix))
153 continue;
154 File lockFile = new File(lockDir, files[i]);
155 if (!lockFile.delete())
156 throw new IOException("Cannot delete " + files[i]);
157 }
158 }
159
160 /** Returns an array of strings, one for each file in the directory. */
161 public final String[] list() throws IOException {
162 return directory.list();
163 }
164
165 /** Returns true iff a file with the given name exists. */
166 public final boolean fileExists(String name) throws IOException {
167 File file = new File(directory, name);
168 return file.exists();
169 }
170
171 /** Returns the time the named file was last modified. */
172 public final long fileModified(String name) throws IOException {
173 File file = new File(directory, name);
174 return file.lastModified();
175 }
176
177 /** Returns the time the named file was last modified. */
178 public static final long fileModified(File directory, String name)
179 throws IOException {
180 File file = new File(directory, name);
181 return file.lastModified();
182 }
183
184 /** Set the modified time of an existing file to now. */
185 public void touchFile(String name) throws IOException {
186 File file = new File(directory, name);
187 file.setLastModified(System.currentTimeMillis());
188 }
189
190 /** Returns the length in bytes of a file in the directory. */
191 public final long fileLength(String name) throws IOException {
192 File file = new File(directory, name);
193 return file.length();
194 }
195
196 /** Removes an existing file in the directory. */
197 public final void deleteFile(String name) throws IOException {
198 File file = new File(directory, name);
199 if (!file.delete())
200 throw new IOException("Cannot delete " + name);
201 }
202
203 /** Renames an existing file in the directory. */
204 public final synchronized void renameFile(String from, String to)
205 throws IOException {
206 File old = new File(directory, from);
207 File nu = new File(directory, to);
208
209 /* This is not atomic. If the program crashes between the call to
210 delete() and the call to renameTo() then we're screwed, but I've
211 been unable to figure out how else to do this... */
212
213 if (nu.exists())
214 if (!nu.delete())
215 throw new IOException("Cannot delete " + to);
216
217 // Rename the old file to the new one. Unfortunately, the renameTo()
218 // method does not work reliably under some JVMs. Therefore, if the
219 // rename fails, we manually rename by copying the old file to the new one
220 if (!old.renameTo(nu)) {
221 java.io.InputStream in = null;
222 java.io.OutputStream out = null;
223 try {
224 in = new FileInputStream(old);
225 out = new FileOutputStream(nu);
226 // see if the buffer needs to be initialized. Initialization is
227 // only done on-demand since many VM's will never run into the renameTo
228 // bug and hence shouldn't waste 1K of mem for no reason.
229 if (buffer == null) {
230 buffer = new byte[1024];
231 }
232 int len;
233 while ((len = in.read(buffer)) >= 0) {
234 out.write(buffer, 0, len);
235 }
236
237 // delete the old file.
238 old.delete();
239 }
240 catch (IOException ioe) {
241 throw new IOException("Cannot rename " + from + " to " + to);
242 }
243 finally {
244 if (in != null) {
245 try {
246 in.close();
247 } catch (IOException e) {
248 throw new RuntimeException("Cannot close input stream: " + e.getMessage());
249 }
250 }
251 if (out != null) {
252 try {
253 out.close();
254 } catch (IOException e) {
255 throw new RuntimeException("Cannot close output stream: " + e.getMessage());
256 }
257 }
258 }
259 }
260 }
261
262 /** Creates a new, empty file in the directory with the given name.
263 Returns a stream writing this file. */
264 public final OutputStream createFile(String name) throws IOException {
265 return new FSOutputStream(new File(directory, name));
266 }
267
268 /** Returns a stream reading an existing file. */
269 public final InputStream openFile(String name) throws IOException {
270 return new FSInputStream(new File(directory, name));
271 }
272
273 /**
274 * So we can do some byte-to-hexchar conversion below
275 */
276 private static final char[] HEX_DIGITS =
277 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
278
279 /** Constructs a {@link Lock} with the specified name. Locks are implemented
280 * with {@link File#createNewFile() }.
281 *
282 * <p>In JDK 1.1 or if system property <I>disableLuceneLocks</I> is the
283 * string "true", locks are disabled. Assigning this property any other
284 * string will <B>not</B> prevent creation of lock files. This is useful for
285 * using Lucene on read-only medium, such as CD-ROM.
286 *
287 * @param name the name of the lock file
288 * @return an instance of <code>Lock</code> holding the lock
289 */
290 public final Lock makeLock(String name) {
291 StringBuffer buf = getLockPrefix();
292 buf.append("-");
293 buf.append(name);
294
295 // create a lock file
296 final File lockFile = new File(lockDir, buf.toString());
297
298 return new Lock() {
299 public boolean obtain() throws IOException {
300 if (DISABLE_LOCKS)
301 return true;
302
303 if (!lockDir.exists()) {
304 if (!lockDir.mkdirs()) {
305 throw new IOException("Cannot create lock directory: " + lockDir);
306 }
307 }
308
309 return lockFile.createNewFile();
310 }
311 public void release() {
312 if (DISABLE_LOCKS)
313 return;
314 lockFile.delete();
315 }
316 public boolean isLocked() {
317 if (DISABLE_LOCKS)
318 return false;
319 return lockFile.exists();
320 }
321
322 public String toString() {
323 return "Lock@" + lockFile;
324 }
325 };
326 }
327
328 private StringBuffer getLockPrefix() {
329 String dirName; // name to be hashed
330 try {
331 dirName = directory.getCanonicalPath();
332 } catch (IOException e) {
333 throw new RuntimeException(e.toString());
334 }
335
336 byte digest[];
337 synchronized (DIGESTER) {
338 digest = DIGESTER.digest(dirName.getBytes());
339 }
340 StringBuffer buf = new StringBuffer();
341 buf.append("lucene-");
342 for (int i = 0; i < digest.length; i++) {
343 int b = digest[i];
344 buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
345 buf.append(HEX_DIGITS[b & 0xf]);
346 }
347
348 return buf;
349 }
350
351 /** Closes the store to future operations. */
352 public final synchronized void close() throws IOException {
353 if (--refCount <= 0) {
354 synchronized (DIRECTORIES) {
355 DIRECTORIES.remove(directory);
356 }
357 }
358 }
359
360 public File getFile() {
361 return directory;
362 }
363
364 /** For debug output. */
365 public String toString() {
366 return "FSDirectory@" + directory;
367 }
368 }
369
370
371 final class FSInputStream extends InputStream {
372 private class Descriptor extends RandomAccessFile {
373 /* DEBUG */
374 //private String name;
375 /* DEBUG */
376 public long position;
377 public Descriptor(File file, String mode) throws IOException {
378 super(file, mode);
379 /* DEBUG */
380 //name = file.toString();
381 //debug_printInfo("OPEN");
382 /* DEBUG */
383 }
384
385 /* DEBUG */
386 //public void close() throws IOException {
387 // debug_printInfo("CLOSE");
388 // super.close();
389 //}
390 //
391 //private void debug_printInfo(String op) {
392 // try { throw new Exception(op + " <" + name + ">");
393 // } catch (Exception e) {
394 // java.io.StringWriter sw = new java.io.StringWriter();
395 // java.io.PrintWriter pw = new java.io.PrintWriter(sw);
396 // e.printStackTrace(pw);
397 // System.out.println(sw.getBuffer().toString());
398 // }
399 //}
400 /* DEBUG */
401 }
402
403 Descriptor file = null;
404 boolean isClone;
405
406 public FSInputStream(File path) throws IOException {
407 file = new Descriptor(path, "r");
408 length = file.length();
409 }
410
411 /** InputStream methods */
412 protected final void readInternal(byte[] b, int offset, int len)
413 throws IOException {
414 synchronized (file) {
415 long position = getFilePointer();
416 if (position != file.position) {
417 file.seek(position);
418 file.position = position;
419 }
420 int total = 0;
421 do {
422 int i = file.read(b, offset+total, len-total);
423 if (i == -1) {
424 throw new IOException("read past EOF");
425 }
426 file.position += i;
427 total += i;
428 } while (total < len);
429 }
430 }
431
432 public final void close() throws IOException {
433 if (!isClone)
434 file.close();
435 }
436
437 /** Random-access methods */
438 protected final void seekInternal(long position) throws IOException {
439 }
440
441 protected final void finalize() throws IOException {
442 close(); // close the file
443 }
444
445 public Object clone() {
446 FSInputStream clone = (FSInputStream)super.clone();
447 clone.isClone = true;
448 return clone;
449 }
450
451 /** Method used for testing. Returns true if the underlying
452 * file descriptor is valid.
453 */
454 boolean isFDValid() throws IOException {
455 return file.getFD().valid();
456 }
457 }
458
459
460 final class FSOutputStream extends OutputStream {
461 RandomAccessFile file = null;
462
463 public FSOutputStream(File path) throws IOException {
464 file = new RandomAccessFile(path, "rw");
465 }
466
467 /** output methods: */
468 public final void flushBuffer(byte[] b, int size) throws IOException {
469 file.write(b, 0, size);
470 }
471 public final void close() throws IOException {
472 super.close();
473 file.close();
474 }
475
476 /** Random-access methods */
477 public final void seek(long pos) throws IOException {
478 super.seek(pos);
479 file.seek(pos);
480 }
481 public final long length() throws IOException {
482 return file.length();
483 }
484
485 protected final void finalize() throws IOException {
486 file.close(); // close the file
487 }
488
489 }
|