package org.eclipse.core.internal.resources;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.internal.events.ILifecycleListener;
import org.eclipse.core.internal.events.LifecycleEvent;
import org.eclipse.core.internal.utils.*;
import org.eclipse.core.internal.watson.ElementTreeIterator;
import org.eclipse.core.internal.watson.IElementContentVisitor;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.content.*;
import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Bundle;
public class ContentDescriptionManager implements IManager, IRegistryChangeListener, IContentTypeManager.IContentTypeChangeListener, ILifecycleListener {
private class FlushJob extends WorkspaceJob {
private final List<IPath> toFlush;
private boolean fullFlush;
public FlushJob() {
super(Messages.resources_flushingContentDescriptionCache);
setSystem(true);
setUser(false);
setPriority(LONG);
setRule(workspace.getRoot());
toFlush = new ArrayList<>(5);
}
@Override
public boolean belongsTo(Object family) {
return FAMILY_DESCRIPTION_CACHE_FLUSH.equals(family);
}
@Override
public IStatus runInWorkspace(final IProgressMonitor monitor) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
try {
monitor.beginTask("", Policy.opWork);
final ISchedulingRule rule = workspace.getRoot();
try {
workspace.prepareOperation(rule, monitor);
workspace.beginOperation(true);
if (systemBundle.getState() != Bundle.STOPPING)
doFlushCache(monitor, getPathsToFlush());
} finally {
workspace.endOperation(rule, false);
}
} catch (OperationCanceledException e) {
return Status.CANCEL_STATUS;
} catch (CoreException e) {
return e.getStatus();
} finally {
monitor.done();
}
return Status.OK_STATUS;
}
private IPath[] getPathsToFlush() {
synchronized (toFlush) {
try {
if (fullFlush)
return null;
int size = toFlush.size();
return (size == 0) ? null : toFlush.toArray(new IPath[size]);
} finally {
fullFlush = false;
toFlush.clear();
}
}
}
void flush(IProject project) {
if (Policy.DEBUG_CONTENT_TYPE_CACHE)
Policy.debug("Scheduling flushing of content type cache for " + (project == null ? Path.ROOT : project.getFullPath()));
synchronized (toFlush) {
if (!fullFlush)
if (project == null)
fullFlush = true;
else
toFlush.add(project.getFullPath());
}
schedule(1000);
}
}
class LazyFileInputStream extends InputStream {
private InputStream actual;
private IFileStore target;
LazyFileInputStream(IFileStore target) {
this.target = target;
}
@Override
public int available() throws IOException {
if (actual == null)
return 0;
return actual.available();
}
@Override
public void close() throws IOException {
if (actual == null)
return;
actual.close();
}
private void ensureOpened() throws IOException {
if (actual != null)
return;
if (target == null)
throw new FileNotFoundException();
try {
actual = target.openInputStream(EFS.NONE, null);
} catch (CoreException e) {
if (e.getCause() instanceof IOException) {
throw (IOException) e.getCause();
}
throw new IOException(e.getMessage());
}
}
@Override
public int read() throws IOException {
ensureOpened();
return actual.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
ensureOpened();
return actual.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
ensureOpened();
return actual.skip(n);
}
}
private static final QualifiedName CACHE_STATE = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "contentCacheState");
private static final QualifiedName CACHE_TIMESTAMP = new QualifiedName(ResourcesPlugin.PI_RESOURCES, "contentCacheTimestamp");
public static final String FAMILY_DESCRIPTION_CACHE_FLUSH = ResourcesPlugin.PI_RESOURCES + ".contentDescriptionCacheFamily";
public static final byte EMPTY_CACHE = 1;
public static final byte USED_CACHE = 2;
public static final byte INVALID_CACHE = 3;
public static final byte FLUSHING_CACHE = 4;
public static final byte ABOUT_TO_FLUSH = 5;
private static final String PT_CONTENTTYPES = "contentTypes";
private Cache cache;
private byte cacheState;
private FlushJob flushJob;
private ProjectContentTypes projectContentTypes;
Workspace workspace;
protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi");
@Override
public void contentTypeChanged(ContentTypeChangeEvent event) {
if (Policy.DEBUG_CONTENT_TYPE)
Policy.debug("Content type settings changed for " + event.getContentType());
invalidateCache(true, null);
}
synchronized void doFlushCache(final IProgressMonitor monitor, IPath[] toClean) throws CoreException {
if (getCacheState() != INVALID_CACHE && getCacheState() != ABOUT_TO_FLUSH) {
if (Policy.DEBUG_CONTENT_TYPE_CACHE)
Policy.debug("Content type cache flush not performed");
return;
}
try {
setCacheState(FLUSHING_CACHE);
cache.discardAll();
if (toClean == null || toClean.length == 0)
clearContentFlags(Path.ROOT, monitor);
else {
for (IPath element : toClean)
clearContentFlags(element, monitor);
}
} catch (CoreException ce) {
setCacheState(INVALID_CACHE);
throw ce;
}
setCacheState(EMPTY_CACHE);
}
private void clearContentFlags(IPath root, final IProgressMonitor monitor) {
long flushStart = System.currentTimeMillis();
if (Policy.DEBUG_CONTENT_TYPE_CACHE)
Policy.debug("Flushing content type cache for " + root);
IElementContentVisitor visitor = (tree, requestor, elementContents) -> {
if (monitor.isCanceled())
throw new OperationCanceledException();
if (elementContents == null)
return false;
ResourceInfo info = (ResourceInfo) elementContents;
if (info.getType() != IResource.FILE)
return true;
info = workspace.getResourceInfo(requestor.requestPath(), false, true);
if (info == null)
return false;
info.clear(ICoreConstants.M_CONTENT_CACHE);
return true;
};
new ElementTreeIterator(workspace.getElementTree(), root).iterate(visitor);
if (Policy.DEBUG_CONTENT_TYPE_CACHE)
Policy.debug("Content type cache for " + root + " flushed in " + (System.currentTimeMillis() - flushStart) + " ms");
}
Cache getCache() {
return cache;
}
public synchronized byte getCacheState() {
if (cacheState != 0)
return cacheState;
String persisted;
try {
persisted = workspace.getRoot().getPersistentProperty(CACHE_STATE);
cacheState = persisted != null ? Byte.parseByte(persisted) : INVALID_CACHE;
} catch (NumberFormatException e) {
cacheState = INVALID_CACHE;
} catch (CoreException e) {
Policy.log(e.getStatus());
cacheState = INVALID_CACHE;
}
return cacheState;
}
public long getCacheTimestamp() throws CoreException {
try {
return Long.parseLong(workspace.getRoot().getPersistentProperty(CACHE_TIMESTAMP));
} catch (NumberFormatException e) {
return 0;
}
}
public IContentTypeMatcher getContentTypeMatcher(Project project) throws CoreException {
return projectContentTypes.getMatcherFor(project);
}
public IContentDescription getDescriptionFor(File file, ResourceInfo info, boolean inSync) throws CoreException {
if (ProjectContentTypes.usesContentTypePreferences(file.getFullPath().segment(0)))
return readDescription(file);
if (getCacheState() == INVALID_CACHE) {
setCacheState(ABOUT_TO_FLUSH);
cache.discardAll();
flushJob.schedule(1000);
}
if (inSync && getCacheState() != ABOUT_TO_FLUSH) {
if (info == null)
return null;
if (info.isSet(ICoreConstants.M_NO_CONTENT_DESCRIPTION))
return null;
if (info.isSet(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION)) {
IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
IContentType type = contentTypeManager.findContentTypeFor(file.getName());
if (type != null)
return type.getDefaultDescription();
info.clear(ICoreConstants.M_CONTENT_CACHE);
}
}
if (inSync) {
synchronized (this) {
Cache.Entry entry = cache.getEntry(file.getFullPath());
if (entry != null && entry.getTimestamp() == getTimestamp(info))
return (IContentDescription) entry.getCached();
}
}
IContentDescription newDescription = readDescription(file);
synchronized (this) {
Cache.Entry entry = cache.getEntry(file.getFullPath());
if (entry != null && inSync && entry.getTimestamp() == getTimestamp(info))
return (IContentDescription) entry.getCached();
if (getCacheState() != ABOUT_TO_FLUSH) {
setCacheState(USED_CACHE);
if (newDescription == null) {
info.set(ICoreConstants.M_NO_CONTENT_DESCRIPTION);
return null;
}
if (newDescription.getContentType().getDefaultDescription().equals(newDescription)) {
IContentType defaultForName = Platform.getContentTypeManager().findContentTypeFor(file.getName());
if (newDescription.getContentType().equals(defaultForName)) {
info.set(ICoreConstants.M_DEFAULT_CONTENT_DESCRIPTION);
return newDescription;
}
}
}
if (entry == null)
entry = cache.addEntry(file.getFullPath(), newDescription, getTimestamp(info));
else {
entry.setTimestamp(getTimestamp(info));
entry.setCached(newDescription);
}
return newDescription;
}
}
private long getTimestamp(ResourceInfo info) {
return info.getContentId() + info.getNodeId();
}
public synchronized void invalidateCache(boolean flush, IProject project) {
if (getCacheState() == EMPTY_CACHE)
return;
try {
setCacheState(INVALID_CACHE);
} catch (CoreException e) {
Policy.log(e.getStatus());
}
if (Policy.DEBUG_CONTENT_TYPE_CACHE)
Policy.debug("Invalidated cache for " + (project == null ? Path.ROOT : project.getFullPath()));
if (flush) {
try {
setCacheState(ABOUT_TO_FLUSH);
cache.discardAll();
} catch (CoreException e) {
Policy.log(e.getStatus());
}
flushJob.flush(project);
}
}
private IContentDescription readDescription(File file) throws CoreException {
if (Policy.DEBUG_CONTENT_TYPE)
Policy.debug("reading contents of " + file);
try (
InputStream contents = new LazyFileInputStream(file.getStore());
) {
IContentTypeMatcher matcher = getContentTypeMatcher((Project) file.getProject());
return matcher.getDescriptionFor(contents, file.getName(), IContentDescription.ALL);
} catch (FileNotFoundException e) {
String message = NLS.bind(Messages.localstore_fileNotFound, file.getFullPath());
throw new ResourceException(IResourceStatus.RESOURCE_NOT_FOUND, file.getFullPath(), message, e);
} catch (IOException e) {
String message = NLS.bind(Messages.resources_errorContentDescription, file.getFullPath());
throw new ResourceException(IResourceStatus.FAILED_DESCRIBING_CONTENTS, file.getFullPath(), message, e);
}
}
@Override
public void registryChanged(IRegistryChangeEvent event) {
if (event.getExtensionDeltas(Platform.PI_RUNTIME, PT_CONTENTTYPES).length == 0)
return;
invalidateCache(true, null);
}
@Override
public void handleEvent(LifecycleEvent event) {
switch (event.kind) {
case LifecycleEvent.POST_PROJECT_CHANGE :
case LifecycleEvent.PRE_PROJECT_DELETE :
case LifecycleEvent.PRE_PROJECT_MOVE :
invalidateCache(true, (IProject) event.resource);
}
}
synchronized void setCacheState(byte newCacheState) throws CoreException {
if (cacheState == newCacheState)
return;
workspace.getRoot().setPersistentProperty(CACHE_STATE, Byte.toString(newCacheState));
cacheState = newCacheState;
}
private void setCacheTimeStamp(long timeStamp) throws CoreException {
workspace.getRoot().setPersistentProperty(CACHE_TIMESTAMP, Long.toString(timeStamp));
}
@Override
public void shutdown(IProgressMonitor monitor) throws CoreException {
if (getCacheState() != INVALID_CACHE)
setCacheTimeStamp(Platform.getStateStamp());
IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
if (contentTypeManager != null)
contentTypeManager.removeContentTypeChangeListener(this);
IExtensionRegistry registry = Platform.getExtensionRegistry();
if (registry != null)
registry.removeRegistryChangeListener(this);
cache.dispose();
cache = null;
flushJob.cancel();
flushJob = null;
projectContentTypes = null;
}
@Override
public void startup(IProgressMonitor monitor) throws CoreException {
workspace = (Workspace) ResourcesPlugin.getWorkspace();
cache = new Cache(100, 1000, 0.1);
projectContentTypes = new ProjectContentTypes(workspace);
getCacheState();
if (cacheState == FLUSHING_CACHE || cacheState == ABOUT_TO_FLUSH)
setCacheState(INVALID_CACHE);
flushJob = new FlushJob();
if (getCacheTimestamp() != Platform.getStateStamp())
invalidateCache(false, null);
workspace.addLifecycleListener(this);
Platform.getContentTypeManager().addContentTypeChangeListener(this);
Platform.getExtensionRegistry().addRegistryChangeListener(this, Platform.PI_RUNTIME);
}
public void projectPreferencesChanged(IProject project) {
if (Policy.DEBUG_CONTENT_TYPE)
Policy.debug("Project preferences changed for " + project);
projectContentTypes.contentTypePreferencesChanged(project);
}
}