KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > resources > ContentDescriptionManager


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.resources;
12
13 import java.io.*;
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16 import org.eclipse.core.filesystem.EFS;
17 import org.eclipse.core.filesystem.IFileStore;
18 import org.eclipse.core.internal.events.ILifecycleListener;
19 import org.eclipse.core.internal.events.LifecycleEvent;
20 import org.eclipse.core.internal.utils.*;
21 import org.eclipse.core.internal.watson.*;
22 import org.eclipse.core.resources.*;
23 import org.eclipse.core.runtime.*;
24 import org.eclipse.core.runtime.content.*;
25 import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent;
26 import org.eclipse.core.runtime.jobs.ISchedulingRule;
27 import org.eclipse.osgi.util.NLS;
28 import org.osgi.framework.Bundle;
29
30 /**
31  * Keeps a cache of recently read content descriptions.
32  *
33  * @since 3.0
34  * @see IFile#getContentDescription()
35  */

36 public class ContentDescriptionManager implements IManager, IRegistryChangeListener, IContentTypeManager.IContentTypeChangeListener, ILifecycleListener {
37     /**
38      * This job causes the content description cache and the related flags
39      * in the resource tree to be flushed.
40      */

41     private class FlushJob extends WorkspaceJob {
42         private final List JavaDoc toFlush;
43         private boolean fullFlush;
44
45         public FlushJob() {
46             super(Messages.resources_flushingContentDescriptionCache);
47             setSystem(true);
48             setUser(false);
49             setPriority(LONG);
50             setRule(workspace.getRoot());
51             toFlush = new ArrayList JavaDoc(5);
52         }
53
54         /* (non-Javadoc)
55          * See Job#belongsTo(Object)
56          */

57         public boolean belongsTo(Object JavaDoc family) {
58             return FAMILY_DESCRIPTION_CACHE_FLUSH.equals(family);
59         }
60
61         /* (non-Javadoc)
62          * See WorkspaceJob#runInWorkspace(IProgressMonitor)
63          */

64         public IStatus runInWorkspace(final IProgressMonitor monitor) {
65             if (monitor.isCanceled())
66                 return Status.CANCEL_STATUS;
67             try {
68                 monitor.beginTask("", Policy.opWork); //$NON-NLS-1$
69
//note that even though we are running in a workspace job, we
70
//must do a begin/endOperation to re-acquire the workspace lock
71
final ISchedulingRule rule = workspace.getRoot();
72                 try {
73                     workspace.prepareOperation(rule, monitor);
74                     workspace.beginOperation(true);
75                     //don't do anything if the system is shutting down or has been shut down
76
//it is too late to change the workspace at this point anyway
77
if (systemBundle.getState() != Bundle.STOPPING)
78                         doFlushCache(monitor, getPathsToFlush());
79                 } finally {
80                     workspace.endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork));
81                 }
82             } catch (OperationCanceledException e) {
83                 return Status.CANCEL_STATUS;
84             } catch (CoreException e) {
85                 return e.getStatus();
86             } finally {
87                 monitor.done();
88             }
89             return Status.OK_STATUS;
90         }
91
92         private IPath[] getPathsToFlush() {
93             synchronized (toFlush) {
94                 try {
95                     if (fullFlush)
96                         return null;
97                     int size = toFlush.size();
98                     return (size == 0) ? null : (IPath[]) toFlush.toArray(new IPath[size]);
99                 } finally {
100                     fullFlush = false;
101                     toFlush.clear();
102                 }
103             }
104         }
105
106         /**
107          * @param project project to flush, or null for a full flush
108          */

109         void flush(IProject project) {
110             if (Policy.DEBUG_CONTENT_TYPE_CACHE)
111                 Policy.debug("Scheduling flushing of content type cache for " + (project == null ? Path.ROOT : project.getFullPath())); //$NON-NLS-1$
112
synchronized (toFlush) {
113                 if (!fullFlush)
114                     if (project == null)
115                         fullFlush = true;
116                     else
117                         toFlush.add(project.getFullPath());
118             }
119             schedule(1000);
120         }
121
122     }
123
124     /**
125      * An input stream that only opens the file if bytes are actually requested.
126      * @see #readDescription(File)
127      */

128     class LazyFileInputStream extends InputStream {
129         private InputStream actual;
130         private IFileStore target;
131
132         LazyFileInputStream(IFileStore target) {
133             this.target = target;
134         }
135
136         public int available() throws IOException {
137             if (actual == null)
138                 return 0;
139             return actual.available();
140         }
141
142         public void close() throws IOException {
143             if (actual == null)
144                 return;
145             actual.close();
146         }
147
148         private void ensureOpened() throws IOException {
149             if (actual != null)
150                 return;
151             if (target == null)
152                 throw new FileNotFoundException();
153             try {
154                 actual = target.openInputStream(EFS.NONE, null);
155             } catch (CoreException e) {
156                 throw new IOException(e.getMessage());
157             }
158         }
159
160         public int read() throws IOException {
161             ensureOpened();
162             return actual.read();
163         }
164
165         public int read(byte[] b, int off, int len) throws IOException {
166             ensureOpened();
167             return actual.read(b, off, len);
168         }
169
170         public long skip(long n) throws IOException {
171             ensureOpened();
172             return actual.skip(n);
173         }
174     }
175
176     private static final QualifiedName CACHE_STATE = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "contentCacheState"); //$NON-NLS-1$
177
private static final QualifiedName CACHE_TIMESTAMP = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "contentCacheTimestamp"); //$NON-NLS-1$\
178

179     public static final String JavaDoc FAMILY_DESCRIPTION_CACHE_FLUSH = ResourcesPlugin.PI_RESOURCES + ".contentDescriptionCacheFamily"; //$NON-NLS-1$
180

181     //possible values for the CACHE_STATE property
182
public static final byte EMPTY_CACHE = 1;
183     public static final byte USED_CACHE = 2;
184     public static final byte INVALID_CACHE = 3;
185     public static final byte FLUSHING_CACHE = 4;
186
187     private static final String JavaDoc PT_CONTENTTYPES = "contentTypes"; //$NON-NLS-1$
188

189     private Cache cache;
190
191     private byte cacheState;
192
193     private FlushJob flushJob;
194     private ProjectContentTypes projectContentTypes;
195
196     Workspace workspace;
197     protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
198

199     /**
200      * @see IContentTypeManager.IContentTypeChangeListener#contentTypeChanged(ContentTypeChangeEvent)
201      */

202     public void contentTypeChanged(ContentTypeChangeEvent event) {
203         if (Policy.DEBUG_CONTENT_TYPE)
204             Policy.debug("Content type settings changed for " + event.getContentType()); //$NON-NLS-1$
205
invalidateCache(true, null);
206     }
207
208     synchronized void doFlushCache(final IProgressMonitor monitor, IPath[] toClean) throws CoreException {
209         // nothing to be done if no information cached
210
if (getCacheState() != INVALID_CACHE) {
211             if (Policy.DEBUG_CONTENT_TYPE_CACHE)
212                 Policy.debug("Content type cache flush not performed"); //$NON-NLS-1$
213
return;
214         }
215         try {
216             setCacheState(FLUSHING_CACHE);
217             // flush the MRU cache
218
cache.discardAll();
219             if (toClean == null || toClean.length == 0)
220                 // no project was added, must be a global flush
221
clearContentFlags(Path.ROOT, monitor);
222             else {
223                 // flush a project at a time
224
for (int i = 0; i < toClean.length; i++)
225                     clearContentFlags(toClean[i], monitor);
226             }
227         } catch (CoreException ce) {
228             setCacheState(INVALID_CACHE);
229             throw ce;
230         }
231         // done cleaning (only if we didn't fail)
232
setCacheState(EMPTY_CACHE);
233     }
234
235     /**
236      * Clears the content related flags for every file under the given root.
237      */

238     private void clearContentFlags(IPath root, final IProgressMonitor monitor) {
239         long flushStart = System.currentTimeMillis();
240         if (Policy.DEBUG_CONTENT_TYPE_CACHE)
241             Policy.debug("Flushing content type cache for " + root); //$NON-NLS-1$
242
// discard content type related flags for all files in the tree
243
IElementContentVisitor visitor = new IElementContentVisitor() {
244             public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object JavaDoc elementContents) {
245                 if (monitor.isCanceled())
246                     throw new OperationCanceledException();
247                 if (elementContents == null)
248                     return false;
249                 ResourceInfo info = (ResourceInfo) elementContents;
250                 if (info.getType() != IResource.FILE)
251                     return true;
252                 info = workspace.getResourceInfo(requestor.requestPath(), false, true);
253                 if (info == null)
254                     return false;
255                 info.clear(ICoreConstants.M_CONTENT_CACHE);
256                 return true;
257             }
258         };
259         new ElementTreeIterator(workspace.getElementTree(), root).iterate(visitor);
260         if (Policy.DEBUG_CONTENT_TYPE_CACHE)
261             Policy.debug("Content type cache for " + root + " flushed in " + (System.currentTimeMillis() - flushStart) + " ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
262
}
263
264     Cache getCache() {
265         return cache;
266     }
267
268     /** Public so tests can examine it. */
269     public synchronized byte getCacheState() {
270         if (cacheState != 0)
271             // we have read/set it before, no nead to read property
272
return cacheState;
273         String JavaDoc persisted;
274         try {
275             persisted = workspace.getRoot().getPersistentProperty(CACHE_STATE);
276             cacheState = persisted != null ? Byte.parseByte(persisted) : INVALID_CACHE;
277         } catch (NumberFormatException JavaDoc e) {
278             cacheState = INVALID_CACHE;
279         } catch (CoreException e) {
280             Policy.log(e.getStatus());
281             cacheState = INVALID_CACHE;
282         }
283         return cacheState;
284     }
285
286     public long getCacheTimestamp() throws CoreException {
287         try {
288             return Long.parseLong(workspace.getRoot().getPersistentProperty(CACHE_TIMESTAMP));
289         } catch (NumberFormatException JavaDoc e) {
290             return 0;
291         }
292     }
293
294     public IContentTypeMatcher getContentTypeMatcher(Project project) throws CoreException {
295         return projectContentTypes.getMatcherFor(project);
296     }
297
298     public IContentDescription getDescriptionFor(File file, ResourceInfo info) throws CoreException {
299         if (ProjectContentTypes.usesContentTypePreferences(file.getFullPath().segment(0)))
300             // caching for project containing project specific settings is not supported
301
return readDescription(file);
302         switch (getCacheState()) {
303             case INVALID_CACHE :
304                 // the cache is not good, flush it
305
flushJob.schedule(1000);
306             //fall through and just read the file
307
case FLUSHING_CACHE :
308                 // the cache is being flushed, but is still not good, just read the file
309
return readDescription(file);
310         }
311         // first look for the flags in the resource info to avoid looking in the cache
312
// don't need to copy the info because the modified bits are not in the deltas
313
if (info == null)
314             return null;
315         if (info.isSet(ICoreConstants.M_NO_CONTENT_DESCRIPTION))
316             // presumably, this file has no known content type
317
return null;
318         if (info.isSet(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION)) {
319             // this file supposedly has a default content description for an "obvious" content type
320
IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
321             // try to find the obvious content type matching its name
322
IContentType type = contentTypeManager.findContentTypeFor(file.getName());
323             if (type != null)
324                 // we found it, we are done
325
return type.getDefaultDescription();
326             // for some reason, there was no content type for this file name
327
// fix this and keep going
328
info.clear(ICoreConstants.M_CONTENT_CACHE);
329         }
330         synchronized (this) {
331             // tries to get a description from the cache
332
Cache.Entry entry = cache.getEntry(file.getFullPath());
333             if (entry != null && entry.getTimestamp() == info.getContentId())
334                 // there was a description in the cache, and it was up to date
335
return (IContentDescription) entry.getCached();
336             // we are going to add an entry to the cache or update the resource info - remember that
337
setCacheState(USED_CACHE);
338             // either we didn't find a description in the cache, or it was not up-to-date - has to be read again
339
IContentDescription newDescription = readDescription(file);
340             if (newDescription == null) {
341                 // no content type exists for this file name/contents - remember this
342
info.set(ICoreConstants.M_NO_CONTENT_DESCRIPTION);
343                 return null;
344             }
345             if (newDescription.getContentType().getDefaultDescription().equals(newDescription)) {
346                 // we got a default description
347
IContentType defaultForName = Platform.getContentTypeManager().findContentTypeFor(file.getName());
348                 if (newDescription.getContentType().equals(defaultForName)) {
349                     // it is a default description for the obvious content type given its file name, we don't have to cache
350
info.set(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION);
351                     return newDescription;
352                 }
353             }
354             // we actually got a description filled by a describer (or a default description for a non-obvious type)
355
if (entry == null)
356                 // there was no entry before - create one
357
entry = cache.addEntry(file.getFullPath(), newDescription, info.getContentId());
358             else {
359                 // just update the existing entry
360
entry.setTimestamp(info.getContentId());
361                 entry.setCached(newDescription);
362             }
363             return newDescription;
364         }
365     }
366
367     /**
368      * Marks the cache as invalid. Does not do anything if the cache is new.
369      * Optionally causes the cached information to be actually flushed.
370      *
371      * @param flush whether the cached information should be flushed
372      * @see #doFlushCache(IProgressMonitor, IPath[])
373      */

374     public synchronized void invalidateCache(boolean flush, IProject project) {
375         if (getCacheState() == EMPTY_CACHE)
376             // cache has not been touched, nothing to do
377
return;
378         // mark the cache as invalid
379
try {
380             setCacheState(INVALID_CACHE);
381         } catch (CoreException e) {
382             Policy.log(e.getStatus());
383         }
384         if (Policy.DEBUG_CONTENT_TYPE_CACHE)
385             Policy.debug("Invalidated cache for " + (project == null ? Path.ROOT : project.getFullPath())); //$NON-NLS-1$
386
if (flush)
387             flushJob.flush(project);
388     }
389
390     /**
391      * Tries to obtain a content description for the given file.
392      */

393     private IContentDescription readDescription(File file) throws CoreException {
394         if (Policy.DEBUG_CONTENT_TYPE)
395             Policy.debug("reading contents of " + file); //$NON-NLS-1$
396
// tries to obtain a description for this file contents
397
InputStream contents = new LazyFileInputStream(file.getStore());
398         try {
399             IContentTypeMatcher matcher = getContentTypeMatcher((Project) file.getProject());
400             return matcher.getDescriptionFor(contents, file.getName(), IContentDescription.ALL);
401         } catch (IOException e) {
402             String JavaDoc message = NLS.bind(Messages.resources_errorContentDescription, file.getFullPath());
403             throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, file.getFullPath(), message, e);
404         } finally {
405             file.ensureClosed(contents);
406         }
407     }
408
409     /**
410      * @see IRegistryChangeListener#registryChanged(IRegistryChangeEvent)
411      */

412     public void registryChanged(IRegistryChangeEvent event) {
413         // no changes related to the content type registry
414
if (event.getExtensionDeltas(Platform.PI_RUNTIME, PT_CONTENTTYPES).length == 0)
415             return;
416         invalidateCache(true, null);
417     }
418
419     /**
420      * @see ILifecycleListener#handleEvent(LifecycleEvent)
421      */

422     public void handleEvent(LifecycleEvent event) {
423         //TODO are these the only events we care about?
424
switch (event.kind) {
425             case LifecycleEvent.PRE_PROJECT_CHANGE :
426             // if the project changes, its natures may have changed as well (content types may be associated to natures)
427
case LifecycleEvent.PRE_PROJECT_DELETE :
428             // if the project gets deleted, we may get confused if it is recreated again (content ids might match)
429
case LifecycleEvent.PRE_PROJECT_MOVE :
430                 // if the project moves, resource paths (used as keys in the in-memory cache) will have changed
431
invalidateCache(true, (IProject) event.resource);
432         }
433     }
434
435     synchronized void setCacheState(byte newCacheState) throws CoreException {
436         if (cacheState == newCacheState)
437             return;
438         workspace.getRoot().setPersistentProperty(CACHE_STATE, Byte.toString(newCacheState));
439         cacheState = newCacheState;
440     }
441
442     private void setCacheTimeStamp(long timeStamp) throws CoreException {
443         workspace.getRoot().setPersistentProperty(CACHE_TIMESTAMP, Long.toString(timeStamp));
444     }
445
446     public void shutdown(IProgressMonitor monitor) throws CoreException {
447         if (getCacheState() != INVALID_CACHE)
448             // remember the platform timestamp for which we have a valid cache
449
setCacheTimeStamp(Platform.getStateStamp());
450         Platform.getContentTypeManager().removeContentTypeChangeListener(this);
451         Platform.getExtensionRegistry().removeRegistryChangeListener(this);
452         cache.dispose();
453         cache = null;
454         flushJob.cancel();
455         flushJob = null;
456         projectContentTypes = null;
457     }
458
459     public void startup(IProgressMonitor monitor) throws CoreException {
460         workspace = (Workspace) ResourcesPlugin.getWorkspace();
461         cache = new Cache(100, 1000, 0.1);
462         projectContentTypes = new ProjectContentTypes(workspace);
463         getCacheState();
464         if (cacheState == FLUSHING_CACHE)
465             // in case we died before completing the last flushing
466
setCacheState(INVALID_CACHE);
467         flushJob = new FlushJob();
468         // the cache is stale (plug-ins that might be contributing content types were added/removed)
469
if (getCacheTimestamp() != Platform.getStateStamp())
470             invalidateCache(false, null);
471         // register a lifecycle listener
472
workspace.addLifecycleListener(this);
473         // register a content type change listener
474
Platform.getContentTypeManager().addContentTypeChangeListener(this);
475         // register a registry change listener
476
Platform.getExtensionRegistry().addRegistryChangeListener(this, Platform.PI_RUNTIME);
477     }
478
479     public void projectPreferencesChanged(IProject project) {
480         if (Policy.DEBUG_CONTENT_TYPE)
481             Policy.debug("Project preferences changed for " + project); //$NON-NLS-1$
482
projectContentTypes.contentTypePreferencesChanged(project);
483     }
484 }
485
Popular Tags