KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > runtime > adaptor > FileManager


1 /*******************************************************************************
2  * Copyright (c) 2004, 2005 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.core.runtime.adaptor;
12
13 import java.io.*;
14 import java.util.*;
15 import org.eclipse.core.runtime.internal.adaptor.BasicLocation;
16 import org.eclipse.core.runtime.internal.adaptor.Locker;
17 import org.eclipse.osgi.framework.internal.reliablefile.*;
18
19 /**
20  * File managers provide a facility for tracking the state of files being used and updated by several
21  * systems at the same time. The typical usecase is in shared configuration data areas.
22  * <p>
23  * The general principle is to maintain a table which maps user-level file name
24  * onto actual disk file. The filename is actually never used, and the file is always stored under the
25  * given filename suffixed by an integer. If a file needs to be modified, it is written into a new file whose name suffix
26  * is incremented.
27  * Once the instance has been created, open() must be called before performing any other operation.
28  * On open the fileManager starts by reading the current table and
29  * thereby obtaining a snapshot of the current directory state. If another
30  * entity updates the directory, the file manager is able to detect the change.
31  * Given that the file is unique, if another entity used the file manager mechanism, the file manager can
32  * still access the state of the file as it was when the file manager first started.
33  * <p>
34  * The facilities provided here are cooperative. That is, all participants must
35  * agree to the conventions and to calling the given API. There is no capacity
36  * to enforce these conventions or prohibit corruption.
37  * </p>
38  * <p>
39  * Clients may not extend this class.
40  * </p>
41  * @since 3.1
42  */

43 public class FileManager {
44     static final int FILETYPE_STANDARD = 0;
45     static final int FILETYPE_RELIABLEFILE = 1;
46     private static boolean tempCleanup = Boolean.valueOf(System.getProperty("osgi.embedded.cleanTempFiles")).booleanValue(); //$NON-NLS-1$
47
private static boolean openCleanup = Boolean.valueOf(System.getProperty("osgi.embedded.cleanupOnOpen")).booleanValue(); //$NON-NLS-1$
48

49     private class Entry {
50         int readId;
51         int writeId;
52         int fileType;
53
54         Entry(int readId, int writeId, int type) {
55             this.readId = readId;
56             this.writeId = writeId;
57             this.fileType = type;
58         }
59
60         int getReadId() {
61             return readId;
62         }
63
64         int getWriteId() {
65             return writeId;
66         }
67
68         int getFileType() {
69             return fileType;
70         }
71
72         void setReadId(int value) {
73             readId = value;
74         }
75
76         void setWriteId(int value) {
77             writeId = value;
78         }
79
80         void setFileType(int type) {
81             fileType = type;
82         }
83     }
84
85     private File base; //The folder managed
86
private File managerRoot; //The folder that will contain all the file related to the functionning of the manager (typically a subdir of base)
87

88     private String JavaDoc lockMode = null;
89     private File tableFile = null;
90     private File lockFile; // The lock file for the table (this file is the same for all the instances)
91
private Locker locker; // The locker for the lock
92

93     private File instanceFile = null; //The file reprensenting the running instance. It is created when the table file is read.
94
private Locker instanceLocker = null; //The locker for the instance file.
95
private boolean readOnly; // Whether this file manager is in read-only mode
96
private boolean open; // Whether this file manager is open for use
97

98     // locking related fields
99
private int tableStamp = -1;
100
101     private Properties table = new Properties();
102
103     private static final String JavaDoc MANAGER_FOLDER = ".manager"; //$NON-NLS-1$
104
private static final String JavaDoc TABLE_FILE = ".fileTable"; //$NON-NLS-1$
105
private static final String JavaDoc LOCK_FILE = ".fileTableLock"; //$NON-NLS-1$
106
private static final int MAX_LOCK_WAIT = 5000; // 5 seconds
107

108     /**
109      * Returns a new file manager for the area identified by the given base
110      * directory.
111      *
112      * @param base the directory holding the files to be managed
113      * @param lockMode the lockMode to use for the given filemanager. It can have one the 3 values: none, java.io, java.nio
114      * and also supports null in which case the lock strategy will be the global one.
115      */

116     public FileManager(File base, String JavaDoc lockMode) {
117         this(base, lockMode, false);
118     }
119
120     /**
121      * Returns a new file manager for the area identified by the given base
122      * directory.
123      *
124      * @param base the directory holding the files to be managed
125      * @param lockMode the lockMode to use for the given filemanager. It can have one the 3 values: none, java.io, java.nio
126      * and also supports null in which case the lock strategy will be the global one.
127      */

128     public FileManager(File base, String JavaDoc lockMode, boolean readOnly) {
129         this.base = base;
130         this.lockMode = lockMode;
131         this.managerRoot = new File(base, MANAGER_FOLDER);
132         if (!readOnly)
133             this.managerRoot.mkdirs();
134         this.tableFile = new File(managerRoot, TABLE_FILE);
135         this.lockFile = new File(managerRoot, LOCK_FILE);
136         this.readOnly = readOnly;
137         open = false;
138     }
139
140     private void initializeInstanceFile() throws IOException {
141         if (instanceFile != null || readOnly)
142             return;
143         this.instanceFile = File.createTempFile(".tmp", ".instance", managerRoot); //$NON-NLS-1$//$NON-NLS-2$
144
this.instanceFile.deleteOnExit();
145         instanceLocker = BasicLocation.createLocker(instanceFile, lockMode);
146         instanceLocker.lock();
147     }
148
149     private String JavaDoc getAbsolutePath(String JavaDoc file) {
150         return new File(base, file).getAbsolutePath();
151     }
152
153     /**
154      * Add the given file name to the list of files managed by this location.
155      *
156      * @param file path of the file to manage
157      * @throws IOException if there are any problems adding the given file to the manager
158      */

159     public void add(String JavaDoc file) throws IOException {
160         add(file, FILETYPE_STANDARD);
161     }
162
163     /**
164      * Add the given file name to the list of files managed by this location.
165      *
166      * @param file path of the file to manage.
167      * @param fileType the file type.
168      * @throws IOException if there are any problems adding the given file to the manager
169      * @see #FILETYPE_STANDARD
170      * @see #FILETYPE_STANDARD
171      */

172     protected void add(String JavaDoc file, int fileType) throws IOException {
173         if (!open)
174             throw new IOException(EclipseAdaptorMsg.fileManager_notOpen);
175         if (readOnly)
176             throw new IOException(EclipseAdaptorMsg.fileManager_illegalInReadOnlyMode);
177         if (!lock(true))
178             throw new IOException(EclipseAdaptorMsg.fileManager_cannotLock);
179         try {
180             updateTable();
181             Entry entry = (Entry) table.get(file);
182             if (entry == null) {
183                 entry = new Entry(0, 1, fileType);
184                 table.put(file, entry);
185                 // if this file existed before, ensure there is not an old
186
// version on the disk to avoid name collisions. If version found,
187
// us the oldest generation+1 for the write ID.
188
int oldestGeneration = findOldestGeneration(file);
189                 if (oldestGeneration != 0)
190                     entry.setWriteId(oldestGeneration + 1);
191                 save();
192             } else {
193                 if (entry.getFileType() != fileType) {
194                     entry.setFileType(fileType);
195                     updateTable();
196                     save();
197                 }
198             }
199         } finally {
200             release();
201         }
202     }
203
204     /**
205      * Find the oldest generation of a file still available on disk
206      * @param file the file from which to obtain the oldest generation.
207      * @return the oldest generation of the file or 0 if the file does
208      * not exist.
209      */

210     private int findOldestGeneration(String JavaDoc file) {
211         String JavaDoc[] files = base.list();
212         int oldestGeneration = 0;
213         if (files != null) {
214             String JavaDoc name = file + '.';
215             int len = name.length();
216             for (int i = 0; i < files.length; i++) {
217                 if (!files[i].startsWith(name))
218                     continue;
219                 try {
220                     int generation = Integer.parseInt(files[i].substring(len));
221                     if (generation > oldestGeneration)
222                         oldestGeneration = generation;
223                 } catch (NumberFormatException JavaDoc e) {
224                     continue;
225                 }
226             }
227         }
228         return oldestGeneration;
229     }
230
231     /**
232      * Update the given target files with the content in the given source files.
233      * The targets are file paths which are currently managed. The sources are
234      * absolute (or relative to the current working directory) file paths
235      * containing the new content for the corresponding target.
236      *
237      * @param targets the target files to update
238      * @param sources the new content for the target files
239      * @throws IOException if there are any problems updating the given files
240      */

241     public void update(String JavaDoc[] targets, String JavaDoc[] sources) throws IOException {
242         if (!open)
243             throw new IOException(EclipseAdaptorMsg.fileManager_notOpen);
244         if (readOnly)
245             throw new IOException(EclipseAdaptorMsg.fileManager_illegalInReadOnlyMode);
246         if (!lock(true))
247             throw new IOException(EclipseAdaptorMsg.fileManager_cannotLock);
248         try {
249             updateTable();
250             int[] originalReadIDs = new int[targets.length];
251             boolean error = false;
252             for (int i = 0; i < targets.length; i++) {
253                 originalReadIDs[i] = getId(targets[i]);
254                 if (!update(targets[i], sources[i]))
255                     error = true;
256             }
257             if (error) {
258                 // restore the original readIDs to avoid inconsistency for this group
259
for (int i = 0; i < targets.length; i++) {
260                     Entry entry = (Entry) table.get(targets[i]);
261                     entry.setReadId(originalReadIDs[i]);
262                 }
263                 throw new IOException(EclipseAdaptorMsg.fileManager_updateFailed);
264             }
265             save(); //save only if no errors
266
} finally {
267             release();
268         }
269     }
270
271     /**
272      * Returns a list of all the file paths currently being managed.
273      *
274      * @return the file paths being managed
275      */

276     public String JavaDoc[] getFiles() {
277         if (!open)
278             return null;
279         Set set = table.keySet();
280         String JavaDoc[] keys = (String JavaDoc[]) set.toArray(new String JavaDoc[set.size()]);
281         String JavaDoc[] result = new String JavaDoc[keys.length];
282         for (int i = 0; i < keys.length; i++)
283             result[i] = new String JavaDoc(keys[i]);
284         return result;
285     }
286
287     /**
288      * Returns the directory containing the files being managed by this file
289      * manager.
290      *
291      * @return the directory containing the managed files
292      */

293     public File getBase() {
294         return base;
295     }
296
297     /**
298      * Returns the current numeric id (appendage) of the given file.
299      * <code>file + "." + getId(file)</code>. -1 is returned if the given
300      * target file is not managed.
301      *
302      * @param target
303      * the managed file to access
304      * @return the id of the file
305      */

306     public int getId(String JavaDoc target) {
307         if (!open)
308             return -1;
309         Entry entry = (Entry) table.get(target);
310         if (entry == null)
311             return -1;
312         return entry.getReadId();
313     }
314
315     /**
316      * Returns the file type for the given file. If the file is not managed or
317      * the file manager is not open, -1 will be returned.
318      *
319      * @param target the managed file to access.
320      * @return the current type this file is being managed as or -1 if an
321      * error occurs.
322      */

323     protected int getFileType(String JavaDoc target) {
324         if (open) {
325             Entry entry = (Entry) table.get(target);
326             if (entry != null)
327                 return entry.getFileType();
328         }
329         return -1;
330     }
331
332     /**
333      * Returns if readOnly state this file manager is using.
334      *
335      * @return if this filemanage update state is read-only.
336      */

337     public boolean isReadOnly() {
338         return readOnly;
339     }
340
341     /**
342      * Attempts to lock the state of this manager and returns <code>true</code>
343      * if the lock could be acquired.
344      * <p>
345      * Locking a manager is advisory only. That is, it does not prevent other
346      * applications from modifying the files managed by this manager.
347      * </p>
348      *
349      * @exception IOException
350      * if there was an unexpected problem while acquiring the
351      * lock.
352      */

353     private boolean lock(boolean wait) throws IOException {
354         if (readOnly)
355             return false;
356         if (locker == null) {
357             locker = BasicLocation.createLocker(lockFile, lockMode);
358             if (locker == null)
359                 throw new IOException(EclipseAdaptorMsg.fileManager_cannotLock);
360         }
361         boolean locked = locker.lock();
362         if (locked || !wait)
363             return locked;
364         //Someone else must have the directory locked, but they should release it quickly
365
long start = System.currentTimeMillis();
366         while (true) {
367             try {
368                 Thread.sleep(200); // 5x per second
369
} catch (InterruptedException JavaDoc e) {/*ignore*/
370             }
371             locked = locker.lock();
372             if (locked)
373                 return true;
374             // never wait longer than 5 seconds
375
long time = System.currentTimeMillis() - start;
376             if (time > MAX_LOCK_WAIT)
377                 return false;
378         }
379     }
380
381     /**
382      * Returns the actual file location to use when reading the given managed file.
383      * <code>null</code> can be returned if the given target is not managed and add is set to false.
384      *
385      * @param target the managed file to lookup
386      * @param add indicate whether the file should be added to the manager if it is not managed.
387      * @throws IOException if the add flag is set to true and the addition of the file failed
388      * @return the absolute file location to use for the given file or
389      * <code>null</code> if the given target is not managed
390      */

391     public File lookup(String JavaDoc target, boolean add) throws IOException {
392         if (!open)
393             throw new IOException(EclipseAdaptorMsg.fileManager_notOpen);
394         Entry entry = (Entry) table.get(target);
395         if (entry == null) {
396             if (add) {
397                 add(target);
398                 entry = (Entry) table.get(target);
399             } else {
400                 return null;
401             }
402         }
403         return new File(getAbsolutePath(target + '.' + entry.getReadId()));
404     }
405
406     private boolean move(String JavaDoc source, String JavaDoc target) {
407         File original = new File(source);
408         File targetFile = new File(target);
409         // its ok if the original does not exist. The table entry will capture
410
// that fact. There is no need to put something in the filesystem.
411
if (!original.exists() || targetFile.exists())
412             return false;
413         return original.renameTo(targetFile);
414     }
415
416     /**
417      * Saves the state of the file manager and releases any locks held.
418      */

419     private void release() {
420         if (locker == null)
421             return;
422         locker.release();
423     }
424
425     /**
426      * Removes the given file from management by this file manager.
427      *
428      * @param file the file to remove
429      */

430     public void remove(String JavaDoc file) throws IOException {
431         if (!open)
432             throw new IOException(EclipseAdaptorMsg.fileManager_notOpen);
433         if (readOnly)
434             throw new IOException(EclipseAdaptorMsg.fileManager_illegalInReadOnlyMode);
435         // The removal needs to be done eagerly, so the value is effectively removed from the disktable.
436
// Otherwise, an updateTable() caused by an update(,) could cause the file to readded to the local table.
437
if (!lock(true))
438             throw new IOException(EclipseAdaptorMsg.fileManager_cannotLock);
439         try {
440             updateTable();
441             table.remove(file);
442             save();
443         } finally {
444             release();
445         }
446     }
447
448     private void updateTable() throws IOException {
449         int stamp;
450         stamp = ReliableFile.lastModifiedVersion(tableFile);
451         if (stamp == tableStamp || stamp == -1)
452             return;
453         Properties diskTable = new Properties();
454         try {
455             InputStream input;
456             input = new ReliableFileInputStream(tableFile);
457             try {
458                 diskTable.load(input);
459             } finally {
460                 input.close();
461             }
462         } catch (IOException e) {
463             throw e; // rethrow the exception, we have nothing to add here
464
}
465         tableStamp = stamp;
466         for (Enumeration e = diskTable.keys(); e.hasMoreElements();) {
467             String JavaDoc file = (String JavaDoc) e.nextElement();
468             String JavaDoc value = diskTable.getProperty(file);
469             if (value != null) {
470                 Entry entry = (Entry) table.get(file);
471                 // check front of value for ReliableFile
472
int id;
473                 int fileType;
474                 int idx = value.indexOf(',');
475                 if (idx != -1) {
476                     id = Integer.parseInt(value.substring(0, idx));
477                     fileType = Integer.parseInt(value.substring(idx + 1));
478                 } else {
479                     id = Integer.parseInt(value);
480                     fileType = FILETYPE_STANDARD;
481                 }
482                 if (entry == null) {
483                     table.put(file, new Entry(id, id + 1, fileType));
484                 } else {
485                     entry.setWriteId(id + 1);
486                     //don't change type
487
}
488             }
489         }
490     }
491
492     /*
493      * This method should be called while the manager is locked.
494      */

495     private void save() throws IOException {
496         if (readOnly)
497             return;
498         // if the table file has change on disk, update our data structures then
499
// rewrite the file.
500
updateTable();
501
502         Properties props = new Properties();
503         for (Enumeration e = table.keys(); e.hasMoreElements();) {
504             String JavaDoc file = (String JavaDoc) e.nextElement();
505             Entry entry = (Entry) table.get(file);
506             String JavaDoc value;
507             if (entry.getFileType() != FILETYPE_STANDARD) {
508                 value = Integer.toString(entry.getWriteId() - 1) + ',' + //In the table we save the write number - 1, because the read number can be totally different. //$NON-NLS-1$
509
Integer.toString(entry.getFileType());
510             } else {
511                 value = Integer.toString(entry.getWriteId() - 1); //In the table we save the write number - 1, because the read number can be totally different.
512
}
513             props.put(file, value);
514         }
515         ReliableFileOutputStream fileStream = new ReliableFileOutputStream(tableFile);
516         try {
517             boolean error = true;
518             try {
519                 props.store(fileStream, "safe table"); //$NON-NLS-1$
520
fileStream.close();
521                 error = false;
522             } finally {
523                 if (error)
524                     fileStream.abort();
525             }
526         } catch (IOException e) {
527             throw new IOException(EclipseAdaptorMsg.fileManager_couldNotSave);
528         }
529         tableStamp = ReliableFile.lastModifiedVersion(tableFile);
530     }
531
532     protected boolean update(String JavaDoc target, String JavaDoc source) {
533         Entry entry = (Entry) table.get(target);
534         int newId = entry.getWriteId();
535         // attempt to rename the file to the next generation
536
boolean success = move(getAbsolutePath(source), getAbsolutePath(target) + '.' + newId);
537         if (!success) {
538             //possible the next write generation file exists? Lets determine the largest
539
//generation number, then use that + 1.
540
newId = findOldestGeneration(target) + 1;
541             success = move(getAbsolutePath(source), getAbsolutePath(target) + '.' + newId);
542         }
543         if (!success)
544             return false;
545         // update the entry. read and write ids should be the same since
546
// everything is in sync
547
entry.setReadId(newId);
548         entry.setWriteId(newId + 1);
549         return true;
550     }
551
552     /**
553      * This methods remove all the temporary files that have been created by the fileManager.
554      * This removal is only done if the instance of eclipse calling this method is the last instance using this fileManager.
555      * @throws IOException
556      */

557     private void cleanup() throws IOException {
558         if (readOnly)
559             return;
560         //Lock first, so someone else can not start while we're in the middle of cleanup
561
if (!lock(true))
562             throw new IOException(EclipseAdaptorMsg.fileManager_cannotLock);
563         try {
564             //Iterate through the temp files and delete them all, except the one representing this filemanager.
565
String JavaDoc[] files = managerRoot.list();
566             if (files != null) {
567                 for (int i = 0; i < files.length; i++) {
568                     if (files[i].endsWith(".instance") && instanceFile != null && !files[i].equalsIgnoreCase(instanceFile.getName())) { //$NON-NLS-1$
569
Locker tmpLocker = BasicLocation.createLocker(new File(managerRoot, files[i]), lockMode);
570                         if (tmpLocker.lock()) {
571                             //If I can lock it is a file that has been left behind by a crash
572
tmpLocker.release();
573                             new File(managerRoot, files[i]).delete();
574                         } else {
575                             tmpLocker.release();
576                             return; //The file is still being locked by somebody else
577
}
578                     }
579                 }
580             }
581
582             //If we are here it is because we are the last instance running. After locking the table and getting its latest content, remove all the backup files and change the table
583
updateTable();
584             Collection managedFiles = table.entrySet();
585             for (Iterator iter = managedFiles.iterator(); iter.hasNext();) {
586                 Map.Entry fileEntry = (Map.Entry) iter.next();
587                 String JavaDoc fileName = (String JavaDoc) fileEntry.getKey();
588                 Entry info = (Entry) fileEntry.getValue();
589                 if (info.getFileType() == FILETYPE_RELIABLEFILE) {
590                     ReliableFile.cleanupGenerations(new File(base, fileName));
591                 } else {
592                     //Because we are cleaning up, we are giving up the values from our table, and we must delete all the files that are not referenced by the table
593
String JavaDoc readId = Integer.toString(info.getWriteId() - 1);
594                     deleteCopies(fileName, readId);
595                 }
596             }
597
598             if (tempCleanup) {
599                 files = base.list();
600                 if (files != null) {
601                     for (int i = 0; i < files.length; i++) {
602                         if (files[i].endsWith(ReliableFile.tmpExt)) { //$NON-NLS-1$
603
new File(base, files[i]).delete();
604                         }
605                     }
606                 }
607             }
608         } catch (IOException e) {
609             //If the exception comes from the updateTable(), there has been a problem in reading the file.
610
//If an exception occured in the save, then the table won't be up to date!
611
throw e;
612         } finally {
613             release();
614         }
615     }
616
617     private void deleteCopies(String JavaDoc fileName, String JavaDoc exceptionNumber) {
618         String JavaDoc notToDelete = fileName + '.' + exceptionNumber;
619         String JavaDoc[] files = base.list();
620         if (files == null)
621             return;
622         for (int i = 0; i < files.length; i++) {
623             if (files[i].startsWith(fileName + '.') && !files[i].equals(notToDelete)) //$NON-NLS-1$
624
new File(base, files[i]).delete();
625         }
626     }
627
628     /**
629      * This methods declare the fileManager as closed. From thereon, the instance can no longer be used.
630      * It is important to close the manager as it also cleanup old copies of the managed files.
631      */

632     public void close() {
633         if (!open)
634             return;
635         open = false;
636         if (readOnly)
637             return;
638         try {
639             cleanup();
640         } catch (IOException e) {
641             //Ignore and close.
642
}
643         if (instanceLocker != null)
644             instanceLocker.release();
645
646         if (instanceFile != null)
647             instanceFile.delete();
648     }
649
650     /**
651      * This methods opens the fileManager, which loads the table in memory. This method must be called before any operation on the filemanager.
652      * @param wait indicates if the open operation must wait in case of contention on the lock file.
653      */

654     public void open(boolean wait) throws IOException {
655         if (openCleanup)
656             cleanup();
657         if (!readOnly) {
658             boolean locked = lock(wait);
659             if (!locked && wait)
660                 throw new IOException(EclipseAdaptorMsg.fileManager_cannotLock);
661         }
662
663         try {
664             initializeInstanceFile();
665             updateTable();
666             open = true;
667         } finally {
668             release();
669         }
670     }
671
672     /**
673      * Creates a new unique empty temporary-file in the filemanager base direcotry. The file name
674      * must be at least 3 characters. This file can later be used to update a managed file.
675      *
676      * @param file the file name to create temporary file from.
677      * @return the newly-created empty file.
678      * @throws IOException if the file can not be created.
679      * @see #update(String[], String[])
680      */

681     public File createTempFile(String JavaDoc file) throws IOException {
682         if (readOnly)
683             throw new IOException(EclipseAdaptorMsg.fileManager_illegalInReadOnlyMode);
684         File tmpFile = File.createTempFile(file, ReliableFile.tmpExt, base);
685         tmpFile.deleteOnExit();
686         return tmpFile;
687     }
688
689 }
Popular Tags