KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > osgi > service > datalocation > FileManager


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

11 package org.eclipse.osgi.service.datalocation;
12
13 import java.io.*;
14 import java.util.*;
15 import org.eclipse.core.runtime.adaptor.*;
16 import org.eclipse.core.runtime.adaptor.BasicLocation;
17 import org.eclipse.core.runtime.adaptor.Locker;
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  */

39 public class FileManager {
40     private class Entry {
41         int readId;
42         int writeId;
43
44         Entry(int readId, int writeId) {
45             this.readId = readId;
46             this.writeId = writeId;
47         }
48
49         int getReadId() {
50             return readId;
51         }
52
53         int getWriteId() {
54             return writeId;
55         }
56
57         void setReadId(int value) {
58             readId = value;
59         }
60
61         void setWriteId(int value) {
62             writeId = value;
63         }
64     }
65
66     private File base; //The folder managed
67
private File managerRoot; //The folder that will contain all the file related to the functionning of the manager (typically a subdir of base)
68

69     private String JavaDoc lockMode = null;
70     private File tableFile = null;
71     private File lockFile; // The lock file for the table (this file is the same for all the instances)
72
private Locker locker; // The locker for the lock
73

74     private File instanceFile = null; //The file reprensenting the running instance. It is created when the table file is read.
75
private Locker instanceLocker = null; //The locker for the instance file.
76

77     // locking related fields
78
private long tableStamp = 0L;
79
80     private Properties table = new Properties();
81
82     private static final String JavaDoc MANAGER_FOLDER = ".manager"; //$NON-NLS-1$
83
private static final String JavaDoc TABLE_FILE = ".fileTable"; //$NON-NLS-1$
84
private static final String JavaDoc LOCK_FILE = ".fileTableLock"; //$NON-NLS-1$
85

86     /**
87      * Returns a new file manager for the area identified by the given base
88      * directory.
89      *
90      * @param base the directory holding the files to be managed
91      * @param lockMode the lockMode to use for the given filemanager. It can have one the 3 values: none, java.io, java.nio
92      * and also supports null in which case the lock strategy will be the global one.
93      */

94     public FileManager(File base, String JavaDoc lockMode) {
95         this.base = base;
96         this.lockMode = lockMode;
97         this.managerRoot = new File(base, MANAGER_FOLDER);
98         this.managerRoot.mkdir();
99         this.tableFile = new File(managerRoot, TABLE_FILE);
100         this.lockFile = new File(managerRoot, LOCK_FILE);
101     }
102
103     private void initializeInstanceFile() throws IOException {
104         if (instanceFile != null)
105             return;
106         this.instanceFile = File.createTempFile(".tmp", ".instance", managerRoot); //$NON-NLS-1$//$NON-NLS-2$
107
this.instanceFile.deleteOnExit();
108         instanceLocker = BasicLocation.createLocker(instanceFile, lockMode);
109         instanceLocker.lock();
110     }
111
112     private String JavaDoc getAbsolutePath(String JavaDoc file) {
113         return new File(base, file).getAbsolutePath();
114     }
115
116     /**
117      * Add the given file name to the list of files managed by this location.
118      *
119      * @param file path of the file to manage
120      * @throws IOException if there are any problems adding the given file to the manager
121      */

122     public void add(String JavaDoc file) throws IOException {
123         if (! lock())
124             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.cannotLock")); //$NON-NLS-1$
125
try {
126             updateTable();
127             Entry entry = (Entry) table.get(file);
128             if (entry == null) {
129                 table.put(file, new Entry(0, 1));
130                 save();
131             }
132         } finally {
133             release();
134         }
135     }
136
137     /**
138      * Update the given target files with the content in the given source files.
139      * The targets are file paths which are currently managed. The sources are
140      * absolute (or relative to the current working directory) file paths
141      * containing the new content for the corresponding target.
142      *
143      * @param targets the target files to update
144      * @param sources the new content for the target files
145      * @throws IOException if there are any problems updating the given files
146      */

147     public void update(String JavaDoc[] targets, String JavaDoc[] sources) throws IOException {
148         if (! lock())
149             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.cannotLock")); //$NON-NLS-1$;
150
try {
151             updateTable();
152             for (int i = 0; i < targets.length; i++) {
153                 String JavaDoc target = targets[i];
154                 update(target, sources[i]);
155             }
156             save();
157         } finally {
158             release();
159         }
160     }
161
162     /**
163      * Returns a list of all the file paths currently being managed.
164      *
165      * @return the file paths being managed
166      */

167     public String JavaDoc[] getFiles() {
168         Set set = table.keySet();
169         String JavaDoc[] keys = (String JavaDoc[]) set.toArray(new String JavaDoc[set.size()]);
170         String JavaDoc[] result = new String JavaDoc[keys.length];
171         for (int i = 0; i < keys.length; i++)
172             result[i] = new String JavaDoc(keys[i]);
173         return result;
174     }
175
176     /**
177      * Returns the directory containing the files being managed by this file
178      * manager.
179      *
180      * @return the directory containing the managed files
181      */

182     public File getBase() {
183         return base;
184     }
185
186     /**
187      * Returns the current numeric id (appendage) of the given file.
188      * <code>file + "." + getId(file)</code>. -1 is returned if the given
189      * target file is not managed.
190      *
191      * @param target
192      * the managed file to access
193      * @return the id of the file
194      */

195     public int getId(String JavaDoc target) {
196         Entry entry = (Entry) table.get(target);
197         if (entry == null)
198             return -1;
199         return entry.getReadId();
200     }
201
202     /**
203      * Attempts to lock the state of this manager and returns <code>true</code>
204      * if the lock could be acquired.
205      * <p>
206      * Locking a manager is advisory only. That is, it does not prevent other
207      * applications from modifying the files managed by this manager.
208      * </p>
209      *
210      * @exception IOException
211      * if there was an unexpected problem while acquiring the
212      * lock.
213      */

214     private boolean lock() throws IOException {
215         if (locker == null)
216             locker = BasicLocation.createLocker(lockFile, lockMode);
217         if (locker == null)
218             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.cannotLock")); //$NON-NLS-1$
219
return locker.lock();
220     }
221
222     /**
223      * Returns the actual file location to use when reading the given managed file.
224      * <code>null</code> can be returned if the given target is not managed and add is set to false.
225      *
226      * @param target the managed file to lookup
227      * @param add indicate whether the file should be added to the manager if it is not managed.
228      * @throws IOException if the add flag is set to true and the addition of the file failed
229      * @return the absolute file location to use for the given file or
230      * <code>null</code> if the given target is not managed
231      */

232     public File lookup(String JavaDoc target, boolean add) throws IOException {
233         Entry entry = (Entry) table.get(target);
234         if (entry == null) {
235             if (add) {
236                 add(target);
237                 entry = (Entry) table.get(target);
238             } else {
239                 return null;
240             }
241         }
242         return new File(getAbsolutePath(target + '.' + entry.getReadId()));
243     }
244
245     private void move(String JavaDoc source, String JavaDoc target) {
246         File original = new File(source);
247         // its ok if the original does not exist. The table entry will capture
248
// that fact. There is no need to put something in the filesystem.
249
if (!original.exists())
250             return;
251         original.renameTo(new File(target));
252     }
253
254     /**
255      * Saves the state of the file manager and releases any locks held.
256      */

257     private void release() {
258         if (locker == null)
259             return;
260         locker.release();
261     }
262
263     /**
264      * Removes the given file from management by this file manager.
265      *
266      * @param file the file to remove
267      */

268     public void remove(String JavaDoc file) throws IOException {
269         // The removal needs to be done eagerly, so the value is effectively removed from the disktable.
270
// Otherwise, an updateTable() caused by an update(,) could cause the file to readded to the local table.
271
if (! lock())
272             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.cannotLock")); //$NON-NLS-1$;
273
try {
274             updateTable();
275             table.remove(file);
276             save();
277         } finally {
278             release();
279         }
280     }
281
282     private void updateTable() throws IOException {
283         if (!tableFile.exists())
284             return;
285         long stamp = tableFile.lastModified();
286         if (stamp == tableStamp)
287             return;
288         initializeInstanceFile();
289         Properties diskTable = new Properties();
290         try {
291             FileInputStream input = new FileInputStream(tableFile);
292             try {
293                 diskTable.load(input);
294             } finally {
295                 input.close();
296             }
297         } catch (IOException e) {
298             throw e; // rethrow the exception, we have nothing to add here
299
}
300         tableStamp = stamp;
301         for (Enumeration e = diskTable.keys(); e.hasMoreElements();) {
302             String JavaDoc file = (String JavaDoc) e.nextElement();
303             String JavaDoc readNumber = diskTable.getProperty(file);
304             if (readNumber != null) {
305                 Entry entry = (Entry) table.get(file);
306                 int id = Integer.parseInt(readNumber);
307                 if (entry == null) {
308                     table.put(file, new Entry(id, id + 1));
309                 } else {
310                     entry.setWriteId(id + 1);
311                 }
312             }
313         }
314     }
315
316     /*
317      * This method should be called while the manager is locked.
318      */

319     private void save() throws IOException {
320         // if the table file has change on disk, update our data structures then
321
// rewrite the file.
322
if (tableStamp != tableFile.lastModified())
323             updateTable();
324         Properties props = new Properties();
325         for (Enumeration e = table.keys(); e.hasMoreElements();) {
326             String JavaDoc file = (String JavaDoc) e.nextElement();
327             Entry entry = (Entry) table.get(file);
328             String JavaDoc value = Integer.toString(entry.getWriteId() - 1); //In the table we save the write number - 1, because the read number can be totally different.
329
props.put(file, value);
330         }
331         FileOutputStream fileStream = new FileOutputStream(tableFile);
332         try {
333             try {
334                 props.store(fileStream, "safe table"); //$NON-NLS-1$
335
} finally {
336                 fileStream.close();
337             }
338         } catch (IOException e) {
339             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.couldNotSave")); //$NON-NLS-1$
340
}
341     }
342
343     private void update(String JavaDoc target, String JavaDoc source) {
344         Entry entry = (Entry) table.get(target);
345         int newId = entry.getWriteId();
346         move(getAbsolutePath(source), getAbsolutePath(target) + '.' + newId);
347         // update the entry. read and write ids should be the same since
348
// everything is in sync
349
entry.setReadId(newId);
350         entry.setWriteId(newId + 1);
351     }
352
353     /**
354      * This methods remove all the temporary files that have been created by the fileManager.
355      * This removal is only done if the instance of eclipse calling this method is the last instance using this fileManager.
356      * @throws IOException
357      */

358     private void cleanup() throws IOException {
359         //Iterate through the temp files and delete them all, except the one representing this filemanager.
360
String JavaDoc[] files = managerRoot.list();
361         for (int i = 0; i < files.length; i++) {
362             if (files[i].endsWith(".instance") && !files[i].equalsIgnoreCase(instanceFile.getName())) { //$NON-NLS-1$
363
if (new File(managerRoot, files[i]).delete() == false)
364                     return;
365             }
366         }
367
368         //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
369
//If the exception comes from lock, another instance may have been started after we cleaned up, therefore we abort
370
if (! lock())
371             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.cannotLock")); //$NON-NLS-1$
372
try {
373             updateTable();
374             Collection managedFiles = table.entrySet();
375             for (Iterator iter = managedFiles.iterator(); iter.hasNext();) {
376                 Map.Entry fileEntry = (Map.Entry) iter.next();
377                 String JavaDoc fileName = (String JavaDoc) fileEntry.getKey();
378                 Entry info = (Entry) fileEntry.getValue();
379                 //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
380
String JavaDoc readId = Integer.toString(info.getWriteId() - 1);
381                 deleteCopies(fileName, readId);
382             }
383         } catch (IOException e) {
384             //If the exception comes from the updateTable(), there has been a problem in reading the file.
385
//If an exception occured in the save, then the table won't be up to date!
386
throw e;
387         } finally {
388             release();
389         }
390     }
391
392     private void deleteCopies(String JavaDoc fileName, String JavaDoc exceptionNumber) {
393         String JavaDoc notToDelete = fileName + '.' + exceptionNumber;
394         String JavaDoc[] files = base.list();
395         for (int i = 0; i < files.length; i++) {
396             if (files[i].startsWith(fileName + '.') && !files[i].equals(notToDelete)) //$NON-NLS-1$
397
new File(base, files[i]).delete();
398         }
399     }
400
401     /**
402      * This methods declare the fileManager as closed. From thereon, the instance can no longer be used.
403      * It is important to close the manager as it also cleanup old copies of the managed files.
404      */

405     public void close() {
406         try {
407             cleanup();
408         } catch (IOException e) {
409             //Ignore and close.
410
}
411         instanceLocker.release();
412         instanceFile.delete();
413     }
414
415     /**
416      * This methods opens the fileManager, which loads the table in memory. This method must be called before any operation on the filemanager.
417      * @param wait indicates if the open operation must wait in case of contention on the lock file.
418      */

419     public void open(boolean wait) throws IOException {
420         boolean locked = lock();
421         if (! locked && wait==false)
422             throw new IOException(EclipseAdaptorMsg.formatter.getString("fileManager.cannotLock")); //$NON-NLS-1$;
423

424         //wait for the lock to be released
425
if (! locked) {
426             do {
427                 try {
428                     Thread.sleep(10);
429                 } catch (InterruptedException JavaDoc e) {
430                     // Ignore the exception and keep waiting
431
}
432             } while(lock());
433         }
434         
435         try {
436             updateTable();
437         } finally {
438             release();
439         }
440     }
441 }
Popular Tags