blob: 9abdb21fe86fc5328ba15b4ea26c55edfe7c8128 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/*
* $RCSfile: BeanInfoCacheController.java,v $
* $Revision: 1.8 $ $Date: 2005/05/11 19:01:28 $
*/
package org.eclipse.jem.internal.beaninfo.core;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.change.impl.EObjectToChangesMapEntryImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.jdt.core.*;
import org.eclipse.jem.internal.beaninfo.adapters.BeaninfoClassAdapter;
import org.eclipse.jem.java.JavaClass;
import org.eclipse.jem.util.emf.workbench.ProjectResourceSet;
import org.eclipse.jem.util.logger.proxy.Logger;
/**
* Controller of the BeanInfo cache. There is one per workspace (it is a static).
*
* The cache is stored on a per IPackageFragmentRoot basis. Each package fragment root will be:
*
* <pre>
*
* root{999}/classname.xmi
*
* </pre>
*
* "root{999}" will be a unigue name (root appended with a number}, one for each package fragment root. The "classname.xmi" will be the BeanInfo cache
* for a class in the root. A root can't have more than one class with the same name, so there shouldn't be any collisions.
* <p>
* Now roots can either be in a project, or they can be an external jar (which can be shared between projects).
* <p>
* Now all roots for a project will be stored in the project's working location
* {@link org.eclipse.core.resources.IProject#getWorkingLocation(java.lang.String)}under the ".cache" directory. It will be this format in each
* project location (under the org.eclipse.jem.beaninfo directory):
*
* <pre>
*
* .index
* root{999}/...
*
* </pre>
*
* The ".index" file will be stored/loaded through an ObjectStream. It will be a {@link BeanInfoCacheController.Index}. It is the index to all of the
* root's in the directory.
* <p>
* All of the external jar roots will be stored in the org.eclipse.jem.beaninfo plugin's state location
* {@link org.eclipse.core.runtime.Platform#getStateLocation(org.osgi.framework.Bundle)}under the ".cache" directory. The format of this directory
* will be the same as for each project. And the roots will be for each unique shared external jar (such as the differnt jre's rt.jars).
* <p>
* Note: There are so many places where synchronization is needed, so it is decided to synchronize only on BeanInfoCacheController.INSTANCE. It would
* be too easy to get a dead-lock because the order of synchronizations can't be easily controlled. Since each piece of sync control is very short
* (except for save of the indices, but that is ok because we don't want them changing while saving) it shouldn't block a lot. There is one place we
* violate this and that is we do a sync on ClassEntry instance when working with the pending. This is necessary because we don't want the cache write
* job to hold up everything while writing, so we sync on the entry being written instead. There we must be very careful that we never to
* BeanInfoCacheControler.INSTANCE sync and then a ClassEntry sync because we could deadlock. The CE access under the covers may do a CE sync and then
* a BeanInfoCacheController.INSTANCE sync.
*
* @since 1.1.0
*/
public class BeanInfoCacheController {
/**
* Singleton cache controller.
*
* @since 1.1.0
*/
public static final BeanInfoCacheController INSTANCE = new BeanInfoCacheController();
private BeanInfoCacheController() {
// Start up save participent.
// TODO For now it only handles final save. It doesn't handle deltas and crashes.
// It should actually be a job that handles in the background, with a way of cache entries being accessed even when it isn't finished. Such
// as until finished when checking mod stamp it will check actual file and compare to cache entry to determine if stale.
saveParticipant = new SaveParticipant();
try {
ResourcesPlugin.getWorkspace().addSaveParticipant(BeaninfoPlugin.getPlugin(), saveParticipant);
} catch (CoreException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e.getStatus());
}
}
protected SaveParticipant saveParticipant;
/**
* An index structure for the Main and Project indexes. Access to the index contents and methods should synchronize on the index itself.
* <p>
* Getting to the index instance should only be through the <code>getMainIndex()</code> and <code>getProjectIndex(IProject)</code> accessors
* so that synchronization and serialization is controlled.
*
* @since 1.1.0
*/
protected static class Index implements Serializable {
private static final long serialVersionUID = 1106864425465L;
/*
* Is this a dirty index, i.e. it has been changed and needs to be saved.
*/
transient private boolean dirty;
/**
* The highest root number used. It is incremented everytime one is needed. It doesn't ever decrease to recover removed roots.
*
* @since 1.1.0
*/
public int highRootNumber;
/**
* Map of root names to the root Index. The key is a {@link IPath}. The path will be relative to the workspace if a project root, or an
* absolute local path to the archive if it is an external archive. It is the IPath to package fragment root (either a folder or a jar file).
* <p>
* The value will be a {@link BeanInfoCacheController.RootIndex}. This is the index for the contents of that root.
*
* @since 1.1.0
*/
transient public Map rootToRootIndex;
/**
* @param dirty
* The dirty to set.
*
* @since 1.1.0
*/
public void setDirty(boolean dirty) {
synchronized (BeanInfoCacheController.INSTANCE) {
this.dirty = dirty;
}
}
/**
* @return Returns the dirty.
*
* @since 1.1.0
*/
public boolean isDirty() {
synchronized (BeanInfoCacheController.INSTANCE) {
return dirty;
}
}
private void writeObject(ObjectOutputStream os) throws IOException {
os.defaultWriteObject();
// Now write out the root to root index map. We are not serializing the Map directly using normal Map serialization because
// the key of the map is an IPath (which is a Path under the covers) and Path is not serializable.
os.writeInt(rootToRootIndex.size());
for (Iterator mapItr = rootToRootIndex.entrySet().iterator(); mapItr.hasNext();) {
Map.Entry entry = (Map.Entry) mapItr.next();
os.writeUTF(((IPath) entry.getKey()).toString());
os.writeObject(entry.getValue());
}
}
private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
is.defaultReadObject();
int size = is.readInt();
rootToRootIndex = new HashMap(size < 100 ? 100 : size);
while (size-- > 0) {
rootToRootIndex.put(new Path(is.readUTF()), is.readObject());
}
}
}
/**
* An index for a root. It has an entry for each class in this root. The class cache entry describes the cache, whether it is stale, what the
* current mod stamp is, etc.
*
* @since 1.1.0
*/
public static abstract class RootIndex implements Serializable {
private static final long serialVersionUID = 1106868674823L;
transient private IPath cachePath; // Absolute local filesystem IPath to the root cache directory. Computed at runtime because it may change
// if workspace relocated.
protected Map classNameToClassEntry; // Map of class names to class entries.
private String rootName; // Name of the root directory in the cache (e.g. "root1").
protected Index index; // Index containing this root index.
protected RootIndex() {
}
public RootIndex(String rootName, Index index) {
this.rootName = rootName;
classNameToClassEntry = new HashMap(100); // When created brand new, put in a map. Otherwise object stream will create the map.
this.index = index;
}
/**
* Return the root directory name
*
* @return rootname
*
* @since 1.1.0
*/
public String getRootName() {
return rootName;
}
/**
* Set this RootIndex (and the containing Index) as being dirty and in need of saving.
*
*
* @since 1.1.0
*/
public void setDirty() {
index.setDirty(true);
}
/*
* Setup for index. It will initialize the path. Once set it won't set it again. This will be called repeatedly by the cache controller
* because there is no way to know if it was lazily created or was brought in from file. When brought in from file the path is not set because
* it should be relocatable and we don't want absolute paths out on the disk caches, and we don't want to waste time creating the path at load
* time because it may not be needed for a while. So it will be lazily created. <p> If the project is set, then the path will be relative to
* the project's working location. If project is <code> null </code> then it will be relative to the BeanInfo plugin's state location. <p>
* This is <package-protected> because only the creator (BeanInfoCacheController class) should set this up.
*
* @param project
*
* @since 1.1.0
*/
void setupIndex(IProject project) {
if (getCachePath() == null)
cachePath = getCacheDir(project).append(rootName);
}
/**
* @return Returns the path of the cache directory for the root.
*
* @since 1.1.0
*/
public IPath getCachePath() {
return cachePath;
}
/**
* Return whether this is a root for a archive or a folder.
*
* @return <code>true</code> if archive for a root. <code>false</code> if archive for a folder.
*
* @since 1.1.0
*/
public abstract boolean isArchiveRoot();
}
/**
* A root index that is for an archive, either internal or external. It contains the archive's modification stamp. Each class cache entry will
* have this same modification stamp. If the archive is changed then all of the class cache entries will be removed because they are all possibly
* stale. No way to know which may be stale and which not.
*
* @since 1.1.0
*/
public static class ArchiveRootIndex extends RootIndex {
private static final long serialVersionUID = 110686867456L;
private long archiveModificationStamp;
/*
* For serializer to call.
*/
protected ArchiveRootIndex() {
}
public ArchiveRootIndex(String rootName, long archiveModificationStamp, Index index) {
super(rootName, index);
this.archiveModificationStamp = archiveModificationStamp;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jem.internal.beaninfo.core.BeanInfoCacheController.RootIndex#isArchiveRoot()
*/
public boolean isArchiveRoot() {
return true;
}
/*
* Set the modification stamp. <p> <package-protected> because only the cache controller should change it.
*
* @param archiveModificationStamp The archiveModificationStamp to set.
*
* @see BeanInfoCacheController#MODIFICATION_STAMP_STALE
* @since 1.1.0
*/
void setArchiveModificationStamp(long archiveModificationStamp) {
this.archiveModificationStamp = archiveModificationStamp;
setDirty();
}
/**
* Returns the modification stamp.
*
* @return Returns the archiveModificationStamp.
* @see BeanInfoCacheController#MODIFICATION_STAMP_STALE
* @since 1.1.0
*/
public long getArchiveModificationStamp() {
return archiveModificationStamp;
}
}
/**
* This is a root index for a folder (which will be in the workspace). Each class cache entry can have a different modification stamp with a
* folder root index.
*
* @since 1.1.0
*/
public static class FolderRootIndex extends RootIndex {
private static final long serialVersionUID = 1106868674842L;
/*
* For serialization.
*/
protected FolderRootIndex() {
}
/**
* @param rootName
*
* @since 1.1.0
*/
public FolderRootIndex(String rootName, Index index) {
super(rootName, index);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jem.internal.beaninfo.core.BeanInfoCacheController.RootIndex#isArchiveRoot()
*/
public boolean isArchiveRoot() {
return false;
}
}
/**
* An individual class entry from the cache. It has an entry for each class in the root. The class cache entry describes the cache, whether it is
* stale, what the current mod stamp is, etc.
* <p>
* There is a method to call to see if deleted. This should only be called if entry is being held on to because
* <code>getClassEntry(JavaClass)</code> will never return a deleted entry. There is a method to get the modification stamp of the current cache
* entry. This is the time stamp of the cooresponding class resource (or archive file) that the cache file was created from. If the value is
* <code>IResource.NULL_STAMP</code>, then the cache file is known to be stale. Otherwise if the value is less than a modification stamp of a
* super class then the cache file is stale.
*
* @see ClassEntry#isDeleted()
* @see ClassEntry#getModificationStamp()
* @see BeanInfoCacheController#getClassEntry(JavaClass)
* @since 1.1.0
*/
public static class ClassEntry implements Serializable {
private static final long serialVersionUID = 1106868674666L;
public static final long DELETED_MODIFICATION_STAMP = Long.MIN_VALUE; // This flag won't be seen externally. It is used to indicate the entry
// has been deleted for those that have been holding a CE.
private long modificationStamp;
private long superModificationStamp; // Stamp of superclass, if any, at time of cache creation.
private String[] interfaceNames; // Interfaces names (null if no interfaces)
private long[] interfaceModicationStamps; // Modification stamps of interfaces, if any. (null if no interfaces).
private transient Resource pendingResource; // Resource is waiting to be saved, but the timestamps are for this pending resource so that we know what it will be ahead of time. At this point the class will be introspected.
private RootIndex rootIndex; // The root index this class entry is in, so that any changes can mark the entry as dirty.
private String className; // The classname for this entry.
private boolean saveOperations; // Flag for saving operations. Once this is set, it will continue to save operations in the cache in addition to everything else.
private long configurationModificationStamp; // Modification stamp of the Eclipse configuration. Used to determine if the cache of override files is out of date due to a config change.
private boolean overrideCacheExists; // Flag that there is an override cache to load. This is orthogonal to the config mod stamp because it simply means that on the current configuration there is no override cache.
private transient Resource pendingOverrideResource; // Override resource is waiting to be saved.
protected ClassEntry() {
}
ClassEntry(RootIndex rootIndex, String className) {
this.setRootIndex(rootIndex);
this.className = className;
modificationStamp = IResource.NULL_STAMP;
rootIndex.classNameToClassEntry.put(className, this);
}
/**
*
* @return
*
* @since 1.1.0
*/
public String getClassName() {
return className;
}
/**
* Return whether the entry has been deleted. This will never be seen in an entry that is still in an index. It is used for entries that have
* been removed from the index. It is for classes (such as the BeanInfoClassAdapter) which are holding onto a copy of entry to let them know.
* <p>
* Holders of entries should call isDeleted if they don't need to further check the mod stamp. Else they should call getModificationStamp and
* check of deleted, stale, or mod stamp. That would save extra synchronizations. If entry is deleted then it should throw the entry away.
*
* @return <code>true</code> if the entry has been deleted, <code>false</code> if still valid.
*
* @see ClassEntry#getModificationStamp()
* @see ClassEntry#isStale()
* @see ClassEntry#DELETED_MODIFICATION_STAMP
* @since 1.1.0
*/
public boolean isDeleted() {
return getModificationStamp() == DELETED_MODIFICATION_STAMP;
}
/**
* Mark the entry as deleted. It will also be removed from root index in that case.
* <p>
* Note: It is public only so that BeanInfoClassAdapter can access it. It should not be called by anyone else outside of BeanInfo.
*/
public synchronized void markDeleted() {
if (!isDeleted()) {
getRootIndex().classNameToClassEntry.remove(className);
setModificationStamp(DELETED_MODIFICATION_STAMP); // Also marks index as dirty.
}
}
/**
* Return whether the entry is stale or not. This orthoganal to isDeleted. isDeleted will not be true if isStale and isStale will not be true
* if isDeleted. Normally you should use getModificationStamp and check the value for IResource.NULL_STAMP and other values to bring it down
* to only one synchronized call to CE, but if only needing to know if stale, you can use this.
*
* @return
*
* @since 1.1.0
* @see IResource#NULL_STAMP
* @see ClassEntry#getModificationStamp()
* @see ClassEntry#isDeleted()
*/
public boolean isStale() {
return getModificationStamp() == IResource.NULL_STAMP;
}
/**
* Return the modification stamp. For those holding onto an entry, and they need to know more than if just deleted, then they should just the
* return value from getModificationStamp. Else they should use isDeleted or isStale.
*
* @return modification stamp, or {@link IResource#NULL_STAMP}if stale or not yet created, or {@link ClassEntry#DELETED_MODIFICATION_STAMP}
* if deleted.
*
* @see ClassEntry#isDeleted()
* @see ClassEntry#isStale()
* @since 1.1.0
*/
public synchronized long getModificationStamp() {
return modificationStamp;
}
/**
* Return the super modification stamp.
* @return
*
* @since 1.1.0
*/
public synchronized long getSuperModificationStamp() {
return superModificationStamp;
}
/**
* Return the interface names or <code>null</code> if no interface names.
* @return
*
* @since 1.1.0
*/
public synchronized String[] getInterfaceNames() {
return interfaceNames;
}
/**
* Return the interface modification stamps or <code>null</code> if no interfaces.
* @return
*
* @since 1.1.0
*/
public synchronized long[] getInterfaceModificationStamps() {
return interfaceModicationStamps;
}
/*
* Set the modification stamp. <p> <package-protected> because only the cache controller should set it. @param modificationStamp
*
* @since 1.1.0
*/
void setModificationStamp(long modificationStamp) {
if (this.modificationStamp != modificationStamp) {
this.modificationStamp = modificationStamp;
getRootIndex().setDirty();
}
}
/**
* Answer whether operations are also stored in the cache for this class. By default they are not. Once turned on they
* will always be stored for this class until the class is deleted.
*
* @return <code>true</code> if operations are cached, <code>false</code> if they are not cached.
*
* @since 1.1.0
*/
public synchronized boolean isOperationsStored() {
return saveOperations;
}
/*
* Set the operations stored flag.
* @param storeOperations
*
* @see BeanInfoCacheController.ClassEntry#isOperationsStored()
* @since 1.1.0
*/
void setOperationsStored(boolean storeOperations) {
saveOperations = storeOperations;
}
/**
* Get the configuration modification stamp of the last saved override cache.
*
* @return
*
* @since 1.1.0
*/
public synchronized long getConfigurationModificationStamp() {
return configurationModificationStamp;
}
/*
* Set the configuration modification stamp.
* <p> <package-protected> because only the cache controller should access it.
*
* @param configurationModificationStamp
*
* @since 1.1.0
*/
void setConfigurationModificationStamp(long configurationModificationStamp) {
this.configurationModificationStamp = configurationModificationStamp;
getRootIndex().setDirty();
}
/**
* Answer whether there is an override cache available.
* @return
*
* @since 1.1.0
*/
public synchronized boolean overrideCacheExists() {
return overrideCacheExists;
}
/*
* Set the override cache exists flag.
* <p> <package-protected> because only the cache controller should access it.
* @param overrideCacheExists
*
* @since 1.1.0
*/
void setOverrideCacheExists(boolean overrideCacheExists) {
this.overrideCacheExists = overrideCacheExists;
}
/**
* Get the pending resource or <code>null</code> if not pending.
*
* @return the pending resource or <code>null</code> if not pending.
*
* @since 1.1.0
*/
public synchronized Resource getPendingResource() {
return pendingResource;
}
/*
* Set the entry. The sequence get,do something,set must be grouped within a synchronized(ClassEntry).
* <p> <package-protected> because only the cache controller should access it.
* @param cacheResource The cacheResource to set.
*
* @since 1.1.0
*/
void setPendingResource(Resource pendingResource) {
this.pendingResource = pendingResource;
}
/**
* Get the pending override resource or <code>null</code> if not pending.
*
* @return the pending override resource or <code>null</code> if not pending.
*
* @since 1.1.0
*/
public synchronized Resource getPendingOverrideResource() {
return pendingOverrideResource;
}
/*
* Set the entry. The sequence get,do something,set must be grouped within a synchronized(ClassEntry).
* <p> <package-protected> because only the cache controller should access it.
* @param cacheResource The cacheResource to set.
*
* @since 1.1.0
*/
void setPendingOverrideResource(Resource pendingOverrideResource) {
this.pendingOverrideResource = pendingOverrideResource;
}
/*
* <package-protected> because only the cache controller should access it. @param rootIndex The rootIndex to set.
*
* @since 1.1.0
*/
void setRootIndex(RootIndex rootIndex) {
this.rootIndex = rootIndex;
}
/*
* <package-protected> because only the cache controller should access it. @return Returns the rootIndex.
*
* @since 1.1.0
*/
RootIndex getRootIndex() {
return rootIndex;
}
/*
* <package-protected> because only the cache controller should access it.
*
* @since 1.1.0
*/
void setSuperModificationStamp(long superModificationStamp) {
this.superModificationStamp = superModificationStamp;
}
/*
* <package-protected> because only the cache controller should access it.
*
* @since 1.1.0
*/
void setInterfaceNames(String[] interfaceNames) {
this.interfaceNames = interfaceNames;
}
/*
* <package-protected> because only the cache controller should access it.
*
* @since 1.1.0
*/
void setInterfaceModificationStamps(long[] interfaceModificationStamps) {
this.interfaceModicationStamps = interfaceModificationStamps;
}
}
/*
* Main index for the external jars. This variable should not be referenced directly except through the getMainIndex() accessor. That controls
* synchronization and restoration as needed.
*/
private static Index MAIN_INDEX;
/*
* Map of project indexes. They are read in as needed. The key will be an IProject and the value will be an Index. This variable should not be
* referenced directly except through the getProjectIndex(IProject) accessor. That controls synchronization and restoration as needed.
*/
private static Map PROJECT_INDEXES = new HashMap();
/*
* Suffix for class cache files.
*/
private static final String CLASS_CACHE_SUFFIX = ".xmi";
/*
* Suffic for class override cache files.
*/
private static final String OVERRIDE_CACHE_SUFFIX = ".override.xmi";
/**
* Return the current class entry for the JavaClass, or <code>null</code> if no current entry.
*
* @param jclass
* @return class entry or <code>null</code> if no current entry.
*
* @since 1.1.0
*/
public ClassEntry getClassEntry(JavaClass jclass) {
IType type = (IType) jclass.getReflectionType();
RootIndex rootIndex = getRootIndex(type);
String className = jclass.getQualifiedNameForReflection();
return getClassEntry(rootIndex, className, false);
}
/**
* Enumeration for newCache: Signals that this cache is the Reflection Cache with no operations in it.
* @since 1.1.0
*/
public final static int REFLECTION_CACHE = 1;
/**
* Enumeration for newCache: Signals that this cache is the Reflection Cache with operations in it.
* @since 1.1.0
*/
public final static int REFLECTION_OPERATIONS_CACHE = 2;
/**
* Enumeration for newCache: Signals that this cache is the Overrides cache.
* @since 1.1.0
*/
public final static int OVERRIDES_CACHE = 3;
/**
* A new cache entry for the given class has been created. Need to write it out.
*
* @param jclass
* the JavaClass the cache is for.
* @param cache
* the ChangeDescription to put out if cacheType is Reflection types, a List if override cache type.
* @param cacheType
* {@link BeanInfoCacheController.ClassEntry#REFLECTION_CACHE} for the enum values.
* @return new class entry (or old one if same one). Should always replace one being held by this one. <code>null</code> if cache could not be
* updated for some reason.
* @since 1.1.0
*/
public ClassEntry newCache(JavaClass jclass, Object cache, int cacheType) {
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER)) {
Logger logger = BeaninfoPlugin.getPlugin().getLogger();
String type = cacheType!=OVERRIDES_CACHE?"Class":"Overrides";
if (cacheType == OVERRIDES_CACHE && cache == null)
type+=" empty";
logger.log("Creating cache for class "+jclass.getQualifiedNameForReflection()+" cache type="+type, Level.FINER);
}
ChangeDescription cd = null;
if (cacheType != OVERRIDES_CACHE) {
// First go through the cd and remove any empty changes. This is because we created the feature change before we knew what went into
// it, and at times nothing goes into it.
cd = (ChangeDescription) cache;
for (Iterator iter = cd.getObjectChanges().iterator(); iter.hasNext();) {
EObjectToChangesMapEntryImpl fcEntry = (EObjectToChangesMapEntryImpl) iter.next();
if (((List) fcEntry.getValue()).isEmpty())
iter.remove(); // Empty changes, remove it.
}
}
IType type = (IType) jclass.getReflectionType();
RootIndex rootIndex = getRootIndex(type);
String className = jclass.getQualifiedNameForReflection();
ClassEntry ce = getClassEntry(rootIndex, className, true); // Create it if not existing.
// Sync on ce so that only can create a cache for a class at a time and so that writing (if occurring at the same time for the class) can be
// held up.
// this is a violation of the agreement to only sync on THIS, but it is necessary or else the write job would lock everything out while it is
// writing. This way it only locks out one class, if the class is at the same time.
// We shouldn't have deadlock because here we lock ce and then THIS (maybe). The write job will lock ce, and then lock THIS. Everywhere else
// we must
// also do lock ce then THIS. Mustn't do other way around or possibility of deadlock. Be careful that any synchronized methods in this class
// do
// not lock an existing ce.
ResourceSet cacheRset = null;
synchronized (ce) {
Resource cres;
if (cacheType != OVERRIDES_CACHE) {
cres = ce.getPendingResource();
if (cres != null) {
// We have a pending, so clear and reuse the resource.
cres.getContents().clear();
} else {
// Not currently writing or waiting to write, so create a new resource.
cres = jclass.eResource().getResourceSet().createResource(
URI.createFileURI(rootIndex.getCachePath().append(className + CLASS_CACHE_SUFFIX).toString()));
}
cacheRset = cres.getResourceSet();
ce.setOperationsStored(cacheType == REFLECTION_OPERATIONS_CACHE);
ce.setPendingResource(cres);
cres.getContents().add(cd);
// Archives use same mod as archive (retrieve from rootindex), while non-archive use the underlying resource's mod stamp.
if (rootIndex.isArchiveRoot())
ce.setModificationStamp(((ArchiveRootIndex) rootIndex).getArchiveModificationStamp());
else {
try {
ce.setModificationStamp(type.getUnderlyingResource().getModificationStamp());
} catch (JavaModelException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
ce.markDeleted(); // Mark as deleted in case this was an existing that someone is holding.
return null; // Couldn't do it, throw cache entry away. This actually should never occur.
}
}
// Need to get the supers info.
List supers = jclass.getESuperTypes();
if (!supers.isEmpty()) {
// We assume that they all have been introspected. This was done back in main introspection. If they are introspected they will have a class entry.
BeaninfoClassAdapter bca = BeaninfoClassAdapter.getBeaninfoClassAdapter((EObject) supers.get(0));
ce.setSuperModificationStamp(bca.getClassEntry().getModificationStamp());
if(supers.size() == 1) {
ce.setInterfaceNames(null);
ce.setInterfaceModificationStamps(null);
} else {
String[] interNames = new String[supers.size()-1];
long[] interMods = new long[interNames.length];
for (int i = 1, indx = 0; i < interNames.length; i++, indx++) {
JavaClass javaClass = (JavaClass) supers.get(i);
bca = BeaninfoClassAdapter.getBeaninfoClassAdapter(javaClass);
bca.introspectIfNecessary(); // Force introspection to get a valid super mod stamp.
interNames[indx] = javaClass.getQualifiedNameForReflection();
interMods[indx] = bca.getClassEntry().getModificationStamp();
}
ce.setInterfaceNames(interNames);
ce.setInterfaceModificationStamps(interMods);
}
} else {
ce.setSuperModificationStamp(IResource.NULL_STAMP);
ce.setInterfaceNames(null);
ce.setInterfaceModificationStamps(null);
}
} else {
// We are an override cache.
if (cache != null) {
cres = ce.getPendingOverrideResource();
if (cres != null) {
// We have a pending, so clear and reuse the resource.
cres.getContents().clear();
} else {
// Not currently writing or waiting to write, so create a new resource.
cres = jclass.eResource().getResourceSet().createResource(
URI.createFileURI(rootIndex.getCachePath().append(className + OVERRIDE_CACHE_SUFFIX).toString()));
}
cacheRset = cres.getResourceSet();
cres.getContents().addAll((List) cache);
ce.setPendingOverrideResource(cres);
ce.setOverrideCacheExists(true);
} else {
ce.setPendingOverrideResource(null);
ce.setOverrideCacheExists(false);
}
ce.setConfigurationModificationStamp(Platform.getPlatformAdmin().getState(false).getTimeStamp());
}
}
queueClassEntry(ce, cacheRset); // Now queue it up.
return ce;
}
/**
* Get the cache resource for the given java class.
* <p>
* NOTE: It is the responsibility of the caller to ALWAYS remove the Resource from its resource set when done with it.
*
* @param jclass
* @param ce the class entry for the jclass
* @param reflectCache <code>true</code> if this the reflection/introspection cache or <code>false</code> if this is the override cache.
* @return the loaded cache resource, or <code>null</code> if not there (for some reason) or an error trying to load it.
*
* @since 1.1.0
*/
public Resource getCache(JavaClass jclass, ClassEntry ce, boolean reflectCache) {
String className = jclass.getQualifiedNameForReflection();
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER)) {
Logger logger = BeaninfoPlugin.getPlugin().getLogger();
String type = reflectCache?"Class":"Overrides";
logger.log("Loading cache for class "+className+" cache type="+type, Level.FINER);
}
if (reflectCache) {
boolean waitForJob = false;
synchronized (ce) {
if (ce.getPendingResource() != null) {
// We have one pending. So wait until write cache job is done, and then load it in.
// Note: Can't just copy the pending resource because it has references to JavaClasses
// and these could be in a different project (since this could be a workspace wide class).
// We would get the wrong java classes then when we apply it.
waitForJob = true;
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER))
BeaninfoPlugin.getPlugin().getLogger().log("Using pending class cache.", Level.FINER);
}
}
if (waitForJob)
waitForCacheSaveJob();
try {
return jclass.eResource().getResourceSet().getResource(
URI.createFileURI(ce.getRootIndex().getCachePath().append(
className + CLASS_CACHE_SUFFIX).toString()), true);
} catch (Exception e) {
// Something happened and couldn't load it.
// TODO - need to remove the Level.INFO arg when the beaninfo cache is working dynamically
BeaninfoPlugin.getPlugin().getLogger().log(e, Level.INFO);
return null;
}
} else {
boolean waitForJob = false;
synchronized (ce) {
if (ce.getPendingOverrideResource() != null) {
// We have one pending. So wait until write cache job is done, and then load it in.
// Note: Can't just copy the pending resource because it has references to JavaClasses
// and these could be in a different project (since this could be a workspace wide class).
// We would get the wrong java classes then when we apply it.
waitForJob = true;
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER))
BeaninfoPlugin.getPlugin().getLogger().log("Using pending override cache.", Level.FINER);
}
}
if (waitForJob)
waitForCacheSaveJob();
try {
return jclass.eResource().getResourceSet().getResource(
URI.createFileURI(ce.getRootIndex().getCachePath().append(
className + OVERRIDE_CACHE_SUFFIX).toString()), true);
} catch (Exception e) {
// Something happened and couldn't load it.
// TODO - need to remove the Level.INFO arg when the beaninfo cache is working dynamically
BeaninfoPlugin.getPlugin().getLogger().log(e, Level.INFO);
return null;
}
}
}
private synchronized ClassEntry getClassEntry(RootIndex rootIndex, String className, boolean createEntry) {
ClassEntry ce = (ClassEntry) rootIndex.classNameToClassEntry.get(className);
if (createEntry && ce == null) {
// Need to create one.
ce = new ClassEntry(rootIndex, className);
// Don't actually mark the rootIndex dirty until the cache for it is actually saved out.
}
return ce;
}
private static final String ROOT_PREFIX = "root";
/*
* Get the root index for the appropriate cache for the given java class.
*/
private RootIndex getRootIndex(IType type) {
IPackageFragmentRoot root = (IPackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
if (!root.isExternal()) {
// So it is in a project. Get the project index.
return getRootIndex(root, root.getJavaProject().getProject());
} else {
// It is an external jar (archive), so needs to come from main index, no project.
return getRootIndex(root, null);
}
}
/*
* Get the root index for the given root. A Project index if project is not null.
*/
private synchronized RootIndex getRootIndex(IPackageFragmentRoot root, IProject project) {
Index index = project != null ? getProjectIndex(project) : getMainIndex();
IPath rootPath = root.getPath();
RootIndex rootIndex = (RootIndex) index.rootToRootIndex.get(rootPath);
if (rootIndex == null) {
// Need to do a new root path.
String rootName = ROOT_PREFIX + (++index.highRootNumber);
rootIndex = root.isArchive() ? createArchiveRootIndex(root, rootName, index) : new FolderRootIndex(rootName, index);
index.rootToRootIndex.put(rootPath, rootIndex);
// Don't set index dirty until we actually save a class cache file. Until then it only needs to be in memory.
}
rootIndex.setupIndex(project); // Set it up, or may already be set, so it will do nothing in that case.
return rootIndex;
}
/*
* Create an archive root with the given root number and root.
*/
private RootIndex createArchiveRootIndex(IPackageFragmentRoot rootArchive, String rootName, Index index) {
long modStamp = IResource.NULL_STAMP;
if (rootArchive.isExternal()) {
modStamp = rootArchive.getPath().toFile().lastModified();
} else {
try {
modStamp = rootArchive.getUnderlyingResource().getModificationStamp();
} catch (JavaModelException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
}
}
return new ArchiveRootIndex(rootName, modStamp, index);
}
private static final String INDEXFILENAME = ".index";
private static final String CACHEDIR = ".cache"; // Cache directory (so as not to conflict with any future BeanInfo Plugin specific data files.
/*
* Get the cache directory for the project (or if project is null, the main plugin cache directory).
*/
// TODO: make this one private
public static IPath getCacheDir(IProject project) {
if (project != null)
return project.getWorkingLocation(BeaninfoPlugin.getPlugin().getBundle().getSymbolicName()).append(CACHEDIR);
else
return BeaninfoPlugin.getPlugin().getStateLocation().append(CACHEDIR);
}
/*
* Get the project index. Synchronized so that we can create it if necessary and not get race conditions.
*/
private synchronized Index getProjectIndex(IProject project) {
Index index = (Index) PROJECT_INDEXES.get(project);
if (index == null) {
// Read the index in.
File indexDirFile = getCacheDir(project).append(INDEXFILENAME).toFile();
if (indexDirFile.canRead()) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(indexDirFile)));
index = (Index) ois.readObject();
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} catch (ClassNotFoundException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} finally {
if (ois != null)
try {
ois.close();
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
}
}
}
if (index == null) {
// Doesn't yet exist or it couldn't be read for some reason, or it was downlevel cache in which case we just throw it away and create
// new).
index = new Index();
index.highRootNumber = 0;
index.rootToRootIndex = new HashMap();
}
PROJECT_INDEXES.put(project, index); // We either created a new one, or we were able to load it. Put it into the map of projects.
}
return index;
}
/*
* Get the main index. Synchronized so that we can create it if necessary and not get race conditions.
*/
private synchronized Index getMainIndex() {
if (MAIN_INDEX == null) {
// Read the index in.
File indexDirFile = getCacheDir(null).append(INDEXFILENAME).toFile();
if (indexDirFile.canRead()) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(indexDirFile)));
MAIN_INDEX = (Index) ois.readObject();
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} catch (ClassNotFoundException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} finally {
if (ois != null)
try {
ois.close();
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
}
}
}
if (MAIN_INDEX == null) {
// Doesn't yet exist or it couldn't be read for some reason, or it was downlevel cache in which case we just throw it away and create
// new).
MAIN_INDEX = new Index();
MAIN_INDEX.highRootNumber = 0;
MAIN_INDEX.rootToRootIndex = new HashMap();
}
}
return MAIN_INDEX;
}
// -------------- Save Participant code -----------------
protected class SaveParticipant implements ISaveParticipant {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.ISaveParticipant#doneSaving(org.eclipse.core.resources.ISaveContext)
*/
public void doneSaving(ISaveContext context) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.ISaveParticipant#prepareToSave(org.eclipse.core.resources.ISaveContext)
*/
public void prepareToSave(ISaveContext context) throws CoreException {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.ISaveParticipant#rollback(org.eclipse.core.resources.ISaveContext)
*/
public void rollback(ISaveContext context) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.ISaveParticipant#saving(org.eclipse.core.resources.ISaveContext)
*/
public void saving(ISaveContext context) throws CoreException {
boolean fullsave = false;
switch (context.getKind()) {
case ISaveContext.PROJECT_SAVE:
IProject project = context.getProject();
synchronized (BeanInfoCacheController.INSTANCE) {
Index projectIndex = (Index) PROJECT_INDEXES.get(project);
if (projectIndex != null && projectIndex.isDirty())
if (cleanIndexDirectory(project, projectIndex))
writeIndex(project, projectIndex);
else {
// It was empty, just get rid of it the index.
PROJECT_INDEXES.remove(project);
}
}
break;
case ISaveContext.FULL_SAVE:
fullsave = true;
waitForCacheSaveJob();
// Now flow into the snapshot save to complete the fullsave.
case ISaveContext.SNAPSHOT:
// For a snapshot, just the dirty indexes, no clean up. If fullsave, cleanup the indexes, but only save the dirty.
synchronized (BeanInfoCacheController.INSTANCE) {
try {
if (MAIN_INDEX != null) {
if (fullsave) {
if (cleanIndexDirectory(null, MAIN_INDEX)) {
if (MAIN_INDEX.isDirty())
writeIndex(null, MAIN_INDEX);
} else {
// It was empty, just get rid of the index.
MAIN_INDEX = null;
}
} else if (MAIN_INDEX.isDirty())
writeIndex(null, MAIN_INDEX);
}
// Now do the project indexes.
for (Iterator projectIndexEntryItr = PROJECT_INDEXES.entrySet().iterator(); projectIndexEntryItr.hasNext();) {
Map.Entry entry = (Map.Entry) projectIndexEntryItr.next();
project = (IProject) entry.getKey();
Index index = (Index) entry.getValue();
if (fullsave) {
if (cleanIndexDirectory(project, index)) {
if (index.isDirty())
writeIndex(project, index);
} else {
// It was empty, just get rid of the index.
projectIndexEntryItr.remove();
}
} else if (index.isDirty())
writeIndex(project, index);
}
} catch (RuntimeException e) {
e.printStackTrace();
// TODO Need to fix the case whereas the cache has been removed
// by Project-->Clean but the memory cache still thinks it exists.
// This try/catch should be remove once that is fixed.
}
}
}
}
/*
* Write an index. Project if not null indicates a project index.
*/
private void writeIndex(IProject project, Index index) {
ObjectOutputStream oos = null;
try {
File indexDirFile = getCacheDir(project).append(INDEXFILENAME).toFile();
oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(indexDirFile)));
oos.writeObject(index);
index.setDirty(false);
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} finally {
if (oos != null)
try {
oos.close();
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
}
}
}
/*
* Clean the index directory of unused root directories. If after cleaning the index is empty, then it will delete the index file too. @return
* true if index not empty, false if index was empty and was erased too.
*/
private boolean cleanIndexDirectory(IProject project, Index index) {
// clean out unused rootIndexes
File indexDir = getCacheDir(project).toFile();
// Create a set of all root names for quick look up.
if (index.rootToRootIndex.isEmpty()) {
// It is empty, clear everything, including index file.
cleanDirectory(indexDir, false);
return false;
} else {
// Need a set of the valid rootnames for quick lookup of names in the directory list.
// And while accumulating this list, clean out the root indexes cache too (i.e. the class cache files).
final Set validFiles = new HashSet(index.rootToRootIndex.size());
validFiles.add(INDEXFILENAME);
for (Iterator itr = index.rootToRootIndex.values().iterator(); itr.hasNext();) {
RootIndex rootIndex = (RootIndex) itr.next();
if (cleanClassCacheDirectory(rootIndex, project)) {
// The class cache has been cleaned, and there are still some classes left, so keep the root index.
validFiles.add(rootIndex.getRootName());
} else {
itr.remove(); // The root index is empty, so get rid of it. Since not a valid name, it will be deleted in next step.
index.setDirty(true); // Also set it dirty in case it wasn't because we need to write out the container Index since it was
// changed.
}
}
// Get list of files and delete those that are not a valid name (used root name, or index file)
String[] fileNames = indexDir.list();
for (int i = 0; i < fileNames.length; i++) {
if (!validFiles.contains(fileNames[i])) {
File file = new File(indexDir, fileNames[i]);
if (file.isDirectory())
cleanDirectory(file, true);
else
file.delete();
}
}
return true;
}
}
/*
* Clean out the class cache directory for the root index. Return true if cleaned good but not empty. Return false if the class cache
* directory is now empty. In this case we should actually get rid of the entire root index.
*/
private boolean cleanClassCacheDirectory(RootIndex rootIndex, IProject project) {
if (rootIndex.classNameToClassEntry.isEmpty())
return false; // There are no classes, so get rid the entire root index.
else {
final Set validFiles = rootIndex.classNameToClassEntry.keySet(); // The keys (classnames) are the filenames (without extension)
// that
// should be kept.
File indexDir = getCacheDir(project).append(rootIndex.getRootName()).toFile();
// Get list of files that are not a valid name (used classname)
String[] fileNames = indexDir.list();
if (fileNames != null) {
for (int i = 0; i < fileNames.length; i++) {
String fileName = fileNames[i];
if (fileName.endsWith(OVERRIDE_CACHE_SUFFIX)) {
// Ends with out class cache extension, see if valid classname.
String classname = fileName.substring(0, fileName.length() - OVERRIDE_CACHE_SUFFIX.length());
ClassEntry ce = (ClassEntry) rootIndex.classNameToClassEntry.get(classname);
if (ce != null && ce.overrideCacheExists())
continue; // It is one of ours. Keep it.
} else if (fileName.endsWith(CLASS_CACHE_SUFFIX)) {
// Ends with out class cache extension, see if valid classname.
if (validFiles.contains(fileName.substring(0, fileName.length() - CLASS_CACHE_SUFFIX.length()))) // Strip down to just
// class and see if
// one of ours.
continue; // It is one of ours. Keep it.
}
// Not valid, get rid of it.
File file = new File(indexDir, fileName);
if (file.isDirectory())
cleanDirectory(file, true);
else
file.delete();
}
}
return true;
}
}
private void cleanDirectory(File dir, boolean eraseDir) {
File[] files = dir.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory())
cleanDirectory(files[i], true);
else
files[i].delete();
}
if (eraseDir)
dir.delete();
}
}
//-------------- Save Class Cache Entry Job -------------------
// This is write queue for class caches. It is a FIFO queue. It is sychronized so that adds/removes are controlled.
// Entries are ClassEntry's. The class entry has the resource that needs to be written out. It will be set to null
// by the job when it is written. The job will have a ClassEntry locked while it is retrieving and resetting the resource
// field in the entry.
//
// The process is the new cache will lock, create resource, set resource into the CE and release lock. Then add the CE to the queue
// and schedule the job (in case job is not running).
//
// The job will lock the CE, get resource from the CE, write it out, set it back to null, release the CE). If the resource is null,
// then it was already processed (this could happen if the job didn't get a chance to save it before another entry was posted
// and this is the second request and it was actually processed by the first request).
// IE:
// 1) resource created, queue entry added
// 2) 2nd req, job not processed yet, resource recreated and put back into CE, new queue entry.
// 3) job pulls from queue, locks ce, grabs resource, writes out the resource, sets back to null, release ce.
// 4) job pulls from queue. This time the resoure is null so it skips it.
//
// Need to lock Ce during entire create and write because the resource set is not reentrant so can't be writing it while creating it.
private List cacheWriteQueue = null;
void waitForCacheSaveJob() {
// For a full save we want to get the class cache files written too, so we need to manipulate the job to get it to finish ASAP.
if (cacheWriteJob != null) {
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER))
BeaninfoPlugin.getPlugin().getLogger().log("Forcing a cache save job to start early.", Level.FINER);
switch (cacheWriteJob.getState()) {
case Job.SLEEPING:
// It could be waiting a long time, so we need to wake it up at a high priority to get it running ASAP.
cacheWriteJob.setPriority(Job.INTERACTIVE); // Need to get it going right away
cacheWriteJob.wakeUp();
// Now drop into the wait.
default:
// Now wait for it (if not running this will return right away).
try {
cacheWriteJob.join();
} catch (InterruptedException e) {
}
}
}
}
static final Map SAVE_CACHE_OPTIONS;
static {
SAVE_CACHE_OPTIONS = new HashMap(3);
SAVE_CACHE_OPTIONS.put(XMLResource.OPTION_SAVE_TYPE_INFORMATION, Boolean.TRUE);
SAVE_CACHE_OPTIONS.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
SAVE_CACHE_OPTIONS.put(XMLResource.OPTION_ENCODING, "UTF-8");
}
protected Job cacheWriteJob = null;
protected Adapter projectReleaseAdapter = new AdapterImpl() {
/* (non-Javadoc)
* @see org.eclipse.emf.common.notify.impl.AdapterImpl#isAdapterForType(java.lang.Object)
*/
public boolean isAdapterForType(Object type) {
return type == BeanInfoCacheController.this; // We're making the BeanInfoCacheController.this be the adapter type.
}
/* (non-Javadoc)
* @see org.eclipse.emf.common.notify.impl.AdapterImpl#notifyChanged(org.eclipse.emf.common.notify.Notification)
*/
public void notifyChanged(Notification msg) {
if (msg.getEventType() == ProjectResourceSet.SPECIAL_NOTIFICATION_TYPE && msg.getFeatureID(BeanInfoCacheController.class) == ProjectResourceSet.PROJECTRESOURCESET_ABOUT_TO_RELEASE_ID) {
// This is an about to be closed. If we have an active write job, bring it up to top priority and wait for it to finish.
// This will make sure any resources in the project are written. There may not be any waiting, but this is doing a close
// project, which is slow already relatively speaking, that waiting for the cache write job to finish is not bad.
waitForCacheSaveJob();
}
}
};
private void queueClassEntry(ClassEntry ce, ResourceSet rset) {
if (cacheWriteQueue == null) {
cacheWriteQueue = Collections.synchronizedList(new LinkedList());
cacheWriteJob = new Job("Write BeanInfo cache files") {
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("", cacheWriteQueue.size() + 10); // This is actually can change during the run, so we add 10 for the heck of it.
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER))
BeaninfoPlugin.getPlugin().getLogger().log("Starting write BeanInfo Cache files.", Level.FINER);
while (!monitor.isCanceled() && !cacheWriteQueue.isEmpty()) {
ClassEntry ce = (ClassEntry) cacheWriteQueue.remove(0); // Get first one.
synchronized (ce) {
Resource cres = ce.getPendingResource();
if (cres != null) {
try {
cres.save(SAVE_CACHE_OPTIONS);
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} finally {
// Remove the resource from resource set, clear out the pending.
cres.getResourceSet().getResources().remove(cres);
ce.setPendingResource(null);
}
}
cres = ce.getPendingOverrideResource();
if (cres != null) {
try {
cres.save(SAVE_CACHE_OPTIONS);
} catch (IOException e) {
BeaninfoPlugin.getPlugin().getLogger().log(e);
} finally {
// Remove the resource from resource set, clear out the pending.
cres.getResourceSet().getResources().remove(cres);
ce.setPendingOverrideResource(null);
}
}
monitor.worked(1);
}
}
monitor.done();
if (BeaninfoPlugin.getPlugin().getLogger().isLoggingLevel(Level.FINER))
BeaninfoPlugin.getPlugin().getLogger().log("Finished write BeanInfo Cache files.", Level.FINER);
return Status.OK_STATUS;
}
};
cacheWriteJob.setPriority(Job.SHORT);
cacheWriteJob.setSystem(true);
}
if (rset != null && EcoreUtil.getExistingAdapter(rset, this) == null) {
// If it is a project resource set, then add ourselves as listeners so we know when released.
if (rset instanceof ProjectResourceSet)
rset.eAdapters().add(projectReleaseAdapter);
}
cacheWriteQueue.add(ce);
cacheWriteJob.schedule(60 * 1000L); // Put off for 1 minute to let other stuff go on. Not important that it happens immediately.
}
}