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