FSDirectory.java
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 createthrows 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 namethrows 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 namethrows 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 namethrows 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 namethrows 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 namethrows 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 namethrows 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 namethrows 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 >> 40xf]);
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 modethrows 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 paththrows 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 positionthrows 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 paththrows IOException {
464     file = new RandomAccessFile(path, "rw");
465   }
466 
467   /** output methods: */
468   public final void flushBuffer(byte[] b, int sizethrows 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 posthrows 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 }