KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > localstore > Bucket


1 /*******************************************************************************
2  * Copyright (c) 2004, 2006 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.internal.localstore;
12
13 import java.io.*;
14 import java.util.*;
15 import org.eclipse.core.internal.resources.ResourceException;
16 import org.eclipse.core.internal.resources.ResourceStatus;
17 import org.eclipse.core.internal.utils.Messages;
18 import org.eclipse.core.resources.IResourceStatus;
19 import org.eclipse.core.runtime.*;
20 import org.eclipse.osgi.util.NLS;
21
22 /**
23  * A bucket is a persistent dictionary having paths as keys. Values are determined
24  * by subclasses.
25  *
26  * @since 3.1
27  */

28 public abstract class Bucket {
29
30     public static abstract class Entry {
31         /**
32          * This entry has not been modified in any way so far.
33          *
34          * @see #state
35          */

36         private final static int STATE_CLEAR = 0;
37         /**
38          * This entry has been requested for deletion.
39          *
40          * @see #state
41          */

42         private final static int STATE_DELETED = 0x02;
43         /**
44          * This entry has been modified.
45          *
46          * @see #state
47          */

48         private final static int STATE_DIRTY = 0x01;
49
50         /**
51          * Logical path of the object we are storing history for. This does not
52          * correspond to a file system path.
53          */

54         private IPath path;
55
56         /**
57          * State for this entry. Possible values are STATE_CLEAR, STATE_DIRTY and STATE_DELETED.
58          *
59          * @see #STATE_CLEAR
60          * @see #STATE_DELETED
61          * @see #STATE_DIRTY
62          */

63         private byte state = STATE_CLEAR;
64
65         protected Entry(IPath path) {
66             this.path = path;
67         }
68
69         public void delete() {
70             state = STATE_DELETED;
71         }
72
73         public abstract int getOccurrences();
74
75         public IPath getPath() {
76             return path;
77         }
78
79         public abstract Object JavaDoc getValue();
80
81         public boolean isDeleted() {
82             return state == STATE_DELETED;
83         }
84
85         public boolean isDirty() {
86             return state == STATE_DIRTY;
87         }
88
89         public boolean isEmpty() {
90             return getOccurrences() == 0;
91         }
92
93         public void markDirty() {
94             Assert.isTrue(state != STATE_DELETED);
95             state = STATE_DIRTY;
96         }
97
98         /**
99          * Called on the entry right after the visitor has visited it.
100          */

101         public void visited() {
102             // does not do anything by default
103
}
104     }
105
106     /**
107      * A visitor for bucket entries.
108      */

109     public static abstract class Visitor {
110         // should continue the traversal
111
public final static int CONTINUE = 0;
112         // should stop looking at states for files in this container (or any of its children)
113
public final static int RETURN = 2;
114
115         /**
116          * Called after the bucket has been visited (and saved).
117          */

118         public void afterSaving(Bucket bucket) throws CoreException {
119             // empty implementation, subclasses to override
120
}
121
122         public void beforeSaving(Bucket bucket) throws CoreException {
123             // empty implementation, subclasses to override
124
}
125
126         /**
127          * @return either STOP, CONTINUE or RETURN
128          */

129         public abstract int visit(Entry entry);
130     }
131
132     /**
133      * The segment name for the root directory for index files.
134      */

135     static final String JavaDoc INDEXES_DIR_NAME = ".indexes"; //$NON-NLS-1$
136

137     /**
138      * Map of the history entries in this bucket. Maps (String -> byte[][]),
139      * where the key is the path of the object we are storing history for, and
140      * the value is the history entry data (UUID,timestamp) pairs.
141      */

142     private final Map entries;
143     /**
144      * The file system location of this bucket index file.
145      */

146     private File location;
147     /**
148      * Whether the in-memory bucket is dirty and needs saving
149      */

150     private boolean needSaving = false;
151     /**
152      * The project name for the bucket currently loaded. <code>null</code> if this is the root bucket.
153      */

154     protected String JavaDoc projectName;
155
156     public Bucket() {
157         this.entries = new HashMap();
158     }
159
160     /**
161      * Applies the given visitor to this bucket index.
162      * @param visitor
163      * @param filter
164      * @param depth the number of trailing segments that can differ from the filter
165      * @return one of STOP, RETURN or CONTINUE constants
166      * @exception CoreException
167      */

168     public final int accept(Visitor visitor, IPath filter, int depth) throws CoreException {
169         if (entries.isEmpty())
170             return Visitor.CONTINUE;
171         try {
172             for (Iterator i = entries.entrySet().iterator(); i.hasNext();) {
173                 Map.Entry mapEntry = (Map.Entry) i.next();
174                 IPath path = new Path((String JavaDoc) mapEntry.getKey());
175                 // check whether the filter applies
176
int matchingSegments = filter.matchingFirstSegments(path);
177                 if (!filter.isPrefixOf(path) || path.segmentCount() - matchingSegments > depth)
178                     continue;
179                 // apply visitor
180
Entry bucketEntry = createEntry(path, mapEntry.getValue());
181                 // calls the visitor passing all uuids for the entry
182
int outcome = visitor.visit(bucketEntry);
183                 // notify the entry it has been visited
184
bucketEntry.visited();
185                 if (bucketEntry.isDeleted()) {
186                     needSaving = true;
187                     i.remove();
188                 } else if (bucketEntry.isDirty()) {
189                     needSaving = true;
190                     mapEntry.setValue(bucketEntry.getValue());
191                 }
192                 if (outcome != Visitor.CONTINUE)
193                     return outcome;
194             }
195             return Visitor.CONTINUE;
196         } finally {
197             visitor.beforeSaving(this);
198             save();
199             visitor.afterSaving(this);
200         }
201     }
202
203     /**
204      * Tries to delete as many empty levels as possible.
205      */

206     private void cleanUp(File toDelete) {
207         if (!toDelete.delete())
208             // if deletion didn't go well, don't bother trying to delete the parent dir
209
return;
210         // don't try to delete beyond the root for bucket indexes
211
if (toDelete.getName().equals(INDEXES_DIR_NAME))
212             return;
213         // recurse to parent directory
214
cleanUp(toDelete.getParentFile());
215     }
216
217     /**
218      * Factory method for creating entries. Subclasses to override.
219      */

220     protected abstract Entry createEntry(IPath path, Object JavaDoc value);
221
222     /**
223      * Flushes this bucket so it has no contents and is not associated to any
224      * location. Any uncommitted changes are lost.
225      */

226     public void flush() {
227         projectName = null;
228         location = null;
229         entries.clear();
230         needSaving = false;
231     }
232
233     /**
234      * Returns how many entries there are in this bucket.
235      */

236     public final int getEntryCount() {
237         return entries.size();
238     }
239
240     /**
241      * Returns the value for entry corresponding to the given path (null if none found).
242      */

243     public final Object JavaDoc getEntryValue(String JavaDoc path) {
244         return entries.get(path);
245     }
246
247     /**
248      * Returns the file name used to persist the index for this bucket.
249      */

250     protected abstract String JavaDoc getIndexFileName();
251
252     /**
253      * Returns the version number for the file format used to persist this bucket.
254      */

255     protected abstract byte getVersion();
256
257     /**
258      * Returns the file name to be used to store bucket version information
259      */

260     protected abstract String JavaDoc getVersionFileName();
261
262     /**
263      * Loads the contents from a file under the given directory.
264      */

265     public void load(String JavaDoc newProjectName, File baseLocation) throws CoreException {
266         load(newProjectName, baseLocation, false);
267     }
268
269     /**
270      * Loads the contents from a file under the given directory. If <code>force</code> is
271      * <code>false</code>, if this bucket already contains the contents from the current location,
272      * avoids reloading.
273      */

274     public void load(String JavaDoc newProjectName, File baseLocation, boolean force) throws CoreException {
275         try {
276             // avoid reloading
277
if (!force && this.location != null && baseLocation.equals(this.location.getParentFile()) && (projectName == null ? (newProjectName == null) : projectName.equals(newProjectName))) {
278                 this.projectName = newProjectName;
279                 return;
280             }
281             // previously loaded bucket may not have been saved... save before loading new one
282
save();
283             this.projectName = newProjectName;
284             this.location = new File(baseLocation, getIndexFileName());
285             this.entries.clear();
286             if (!this.location.isFile())
287                 return;
288             DataInputStream source = new DataInputStream(new BufferedInputStream(new FileInputStream(location), 8192));
289             try {
290                 int version = source.readByte();
291                 if (version != getVersion()) {
292                     // unknown version
293
String JavaDoc message = NLS.bind(Messages.resources_readMetaWrongVersion, location.getAbsolutePath(), Integer.toString(version));
294                     ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, message);
295                     throw new ResourceException(status);
296                 }
297                 int entryCount = source.readInt();
298                 for (int i = 0; i < entryCount; i++)
299                     this.entries.put(readEntryKey(source), readEntryValue(source));
300             } finally {
301                 source.close();
302             }
303         } catch (IOException ioe) {
304             String JavaDoc message = NLS.bind(Messages.resources_readMeta, location.getAbsolutePath());
305             ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, null, message, ioe);
306             throw new ResourceException(status);
307         }
308     }
309
310     private String JavaDoc readEntryKey(DataInputStream source) throws IOException {
311         if (projectName == null)
312             return source.readUTF();
313         return IPath.SEPARATOR + projectName + source.readUTF();
314     }
315
316     /**
317      * Defines how data for a given entry is to be read from a bucket file. To be implemented by subclasses.
318      */

319     protected abstract Object JavaDoc readEntryValue(DataInputStream source) throws IOException, CoreException;
320
321     /**
322      * Saves this bucket's contents back to its location.
323      */

324     public void save() throws CoreException {
325         if (!needSaving)
326             return;
327         try {
328             if (entries.isEmpty()) {
329                 needSaving = false;
330                 cleanUp(location);
331                 return;
332             }
333             // ensure the parent location exists
334
File parent = location.getParentFile();
335             if (parent == null)
336                 throw new IOException();//caught and rethrown below
337
parent.mkdirs();
338             DataOutputStream destination = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(location), 8192));
339             try {
340                 destination.write(getVersion());
341                 destination.writeInt(entries.size());
342                 for (Iterator i = entries.entrySet().iterator(); i.hasNext();) {
343                     Map.Entry entry = (Map.Entry) i.next();
344                     writeEntryKey(destination, (String JavaDoc) entry.getKey());
345                     writeEntryValue(destination, entry.getValue());
346                 }
347             } finally {
348                 destination.close();
349             }
350             needSaving = false;
351         } catch (IOException ioe) {
352             String JavaDoc message = NLS.bind(Messages.resources_writeMeta, location.getAbsolutePath());
353             ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, null, message, ioe);
354             throw new ResourceException(status);
355         }
356     }
357
358     /**
359      * Sets the value for the entry with the given path. If <code>value</code> is <code>null</code>,
360      * removes the entry.
361      */

362     public final void setEntryValue(String JavaDoc path, Object JavaDoc value) {
363         if (value == null)
364             entries.remove(path);
365         else
366             entries.put(path, value);
367         needSaving = true;
368     }
369
370     private void writeEntryKey(DataOutputStream destination, String JavaDoc path) throws IOException {
371         if (projectName == null) {
372             destination.writeUTF(path);
373             return;
374         }
375         // omit the project name
376
int pathLength = path.length();
377         int projectLength = projectName.length();
378         String JavaDoc key = (pathLength == projectLength + 1) ? "" : path.substring(projectLength + 1); //$NON-NLS-1$
379
destination.writeUTF(key);
380     }
381
382     /**
383      * Defines how an entry is to be persisted to the bucket file.
384      */

385     protected abstract void writeEntryValue(DataOutputStream destination, Object JavaDoc entryValue) throws IOException, CoreException;
386 }
387
Popular Tags