package org.objectstyle.wolips.bindings.wod;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.objectstyle.wolips.bindings.api.ApiCache;
import org.objectstyle.wolips.bindings.utils.BindingReflectionUtils;
import org.objectstyle.wolips.core.resources.types.LimitedLRUCache;
import org.objectstyle.wolips.core.resources.types.SubTypeHierarchyCache;
import org.objectstyle.wolips.core.resources.types.SuperTypeHierarchyCache;
public class TypeCache {
private Map<IJavaProject, ApiCache> _apiCache;
private LimitedLRUCache<IType, TypeCacheEntry> _typeCacheEntries;
public TypeCache() {
_typeCacheEntries = new LimitedLRUCache<IType, TypeCacheEntry>(1000);
_apiCache = new HashMap<IJavaProject, ApiCache>();
}
public TypeCacheEntry getTypeCacheEntry(IType type) throws JavaModelException {
synchronized (_typeCacheEntries) {
TypeCacheEntry entry = _typeCacheEntries.get(type);
if (entry == null) {
entry = new TypeCacheEntry(type);
_typeCacheEntries.put(type, entry);
}
return entry;
}
}
public ApiCache getApiCache(IJavaProject javaProject) {
ApiCache apiCache;
if (javaProject == null) {
apiCache = new ApiCache();
}
else {
synchronized (_apiCache) {
apiCache = _apiCache.get(javaProject);
if (apiCache == null) {
apiCache = new ApiCache();
_apiCache.put(javaProject, apiCache);
}
}
}
return apiCache;
}
public List<BindingValueKey> getBindingValueAccessorKeys(IJavaProject javaProject, IType type, String name) throws JavaModelException {
return getTypeCacheEntry(type).getBindingValueAccessorKeys(javaProject, name);
}
public List<BindingValueKey> getBindingValueMutatorKeys(IJavaProject javaProject, IType type, String name) throws JavaModelException {
return getTypeCacheEntry(type).getBindingValueMutatorKeys(javaProject, name);
}
public void clearCacheForProject(IProject project) {
if (project != null) {
//System.out.println("TypeCache.clearCacheForProject: CLEARING " + project);
List<IType> typesToClear = new LinkedList<IType>();
synchronized (_typeCacheEntries) {
for (Map.Entry<IType, TypeCacheEntry> entry : _typeCacheEntries.entrySet()) {
IResource resource = entry.getValue().getResource();
if (resource != null && project.equals(resource.getProject())) {
typesToClear.add(entry.getKey());
}
}
for (IType typeToClear : typesToClear) {
clearCacheForType(typeToClear);
}
}
}
}
public void clearCacheForResource(IResource resource) {
if (resource != null) {
List<IType> typesToClear = new LinkedList<IType>();
synchronized (_typeCacheEntries) {
for (Map.Entry<IType, TypeCacheEntry> entry : _typeCacheEntries.entrySet()) {
if (resource.equals(entry.getValue().getResource())) {
typesToClear.add(entry.getKey());
}
}
for (IType typeToClear : typesToClear) {
clearCacheForType(typeToClear);
}
}
}
}
public void clearCacheForType(IType declaringType) {
synchronized (_typeCacheEntries) {
//System.out.println("TypeCache.clearCacheForType: clearing cache for " + declaringType.getFullyQualifiedName());
_typeCacheEntries.remove(declaringType);
}
}
public IType getTypeForNameInType(String typeName, IType declaringType) throws JavaModelException {
return getTypeCacheEntry(declaringType).getTypeForName(typeName);
}
public void clearCache() {
synchronized (_typeCacheEntries) {
_typeCacheEntries.clear();
}
}
public List<IType> getSupertypesOf(IType type) throws JavaModelException {
//System.out.println("TypeCache.getSupertypesOf: " + type.getFullyQualifiedName() + " (hits=" + SuperTypeHierarchyCache.getCacheHits() + ",misses=" + SuperTypeHierarchyCache.getCacheMisses() + ")");
try {
return getTypeCacheEntry(type).getSupertypes();
}
catch (JavaModelException e) {
clearCacheForType(type);
throw e;
}
}
public List<IType> getSubtypesOfInProject(IType type, IJavaProject project) throws JavaModelException {
try {
return getTypeCacheEntry(type).getSubtypesInProject(project);
}
catch (JavaModelException e) {
clearCacheForType(type);
throw e;
}
}
public class TypeCacheEntry {
private IType _type;
private IResource _resource;
private Map<String, IType> _nextTypeCache;
private Map<String, List<BindingValueKey>> _bindingValueAccessorKeys;
private Map<String, List<BindingValueKey>> _bindingValueMutatorKeys;
public TypeCacheEntry(IType type) throws JavaModelException {
_type = type;
//_resource = _type.getUnderlyingResource();
_nextTypeCache = new HashMap<String, IType>();
_bindingValueAccessorKeys = new HashMap<String, List<BindingValueKey>>();
_bindingValueMutatorKeys = new HashMap<String, List<BindingValueKey>>();
}
public IResource getResource() {
return _resource;
}
public List<BindingValueKey> getBindingValueAccessorKeys(IJavaProject javaProject, String name) throws JavaModelException {
synchronized (_bindingValueAccessorKeys) {
List<BindingValueKey> bindingValueAccessorKeys = _bindingValueAccessorKeys.get(name);
//System.out.println("TypeCacheEntry.getBindingValueAccessorKeys: " + name + ": " + bindingValueAccessorKeys);
if (bindingValueAccessorKeys == null) {
//System.out.println("TypeCache.getBindingValueAccessorKeys: MISS " + type.getElementName() + ": " + name);
bindingValueAccessorKeys = BindingReflectionUtils.getBindingKeys(javaProject, _type, name, true, BindingReflectionUtils.ACCESSORS_OR_VOID, false, TypeCache.this);
// MS: Don't cache this for now -- I don't know how many end up in here and how long they
// hang around, but I think the answer is "a lot" and "for a long time". However, it's a huge performance win.
// Q: Don't cache results from types with generic type parameters
if (_type.getTypeParameters().length == 0 || bindingValueAccessorKeys.size() == 0) {
_bindingValueAccessorKeys.put(name, bindingValueAccessorKeys);
} else {
//System.out.println("TypeCacheEntry.getBindingValueMutatorKeys: not caching " + _type.getElementName() + ": " + name);
}
}
else {
//System.out.println("TypeCache.getBindingValueAccessorKeys: HIT " + _type.getElementName() + ": " + name);
}
return bindingValueAccessorKeys;
}
}
public List<BindingValueKey> getBindingValueMutatorKeys(IJavaProject javaProject, String name) throws JavaModelException {
synchronized (_bindingValueMutatorKeys) {
List<BindingValueKey> bindingValueMutatorKeys = _bindingValueMutatorKeys.get(name);
if (bindingValueMutatorKeys == null) {
//System.out.println("TypeCache.getBindingValueMutatorKeys: MISS " + type.getElementName() + ": " + name);
bindingValueMutatorKeys = BindingReflectionUtils.getBindingKeys(javaProject, _type, name, true, BindingReflectionUtils.MUTATORS_ONLY, false, TypeCache.this);
// MS: Don't cache this for now -- I don't know how many end up in here and how long they
// hang around, but I think the answer is "a lot" and "for a long time". However, it's a huge performance win.
// Q: Don't cache results from types with generic type parameters
if (_type.getTypeParameters().length == 0 && bindingValueMutatorKeys.size() > 0) {
_bindingValueMutatorKeys.put(name, bindingValueMutatorKeys);
} else {
//System.out.println("TypeCacheEntry.getBindingValueMutatorKeys: not caching " + _type.getElementName() + ": " + name);
}
}
else {
//System.out.println("TypeCache.getBindingValueMutatorKeys: HIT " + _type.getElementName() + ": " + name);
}
return bindingValueMutatorKeys;
}
}
/**
* Resolves a type name in the context of the declaring type.
*
* @param refTypeSig the type name in signature notation (for example 'QVector') this can also be an array type, but dimensions will be ignored.
* @param declaringType the context for resolving (type where the reference was made in)
* @return returns the fully qualified type name or build-in-type name. if a unresolved type couldn't be resolved null is returned
* @throws JavaModelException thrown when the type can not be accessed
*/
public IType resolveType(String refTypeSig, IType declaringType) throws JavaModelException {
IJavaProject javaProject = declaringType.getJavaProject();
int arrayCount= Signature.getArrayCount(refTypeSig);
char type= refTypeSig.charAt(arrayCount);
if (type == Signature.C_UNRESOLVED) {
String name= ""; //$NON-NLS-1$
int bracket= refTypeSig.indexOf(Signature.C_GENERIC_START, arrayCount + 1);
if (bracket > 0)
name= refTypeSig.substring(arrayCount + 1, bracket);
else {
int semi= refTypeSig.indexOf(Signature.C_SEMICOLON, arrayCount + 1);
if (semi == -1) {
throw new IllegalArgumentException();
}
name= refTypeSig.substring(arrayCount + 1, semi);
}
String dotTypeName = "." + name;
String dotBaseTypeName = null;
String dotExtensionTypeName = null;
int dotIndex = name.indexOf('.');
if (dotIndex != -1) {
// We might get a fully qualified type name -- Qcom.apple.jingle.eo.MZProgramNodeType;
IType resolvedType = javaProject.findType(name);
if (resolvedType != null) {
return resolvedType;
}
// If not, then this might be a nested type reference on another type, so let's split it to look for that in our imports later
dotBaseTypeName = "." + name.substring(0, dotIndex);
dotExtensionTypeName = name.substring(dotIndex);
}
IImportDeclaration[] importDeclarations = declaringType.getCompilationUnit().getImports();
// Loop over the imports and look for the import of our symbol
for (IImportDeclaration declaration : importDeclarations) {
String importName = declaration.getElementName();
// If it's a .* import, then pop off the package name and lookup the type
if (declaration.isOnDemand()) {
String packageName = importName.substring(0, importName.lastIndexOf('.'));
String possibleTypeName = packageName + dotTypeName;
IType onDemandPackageType = javaProject.findType(possibleTypeName);
if (onDemandPackageType != null) {
return onDemandPackageType;
}
}
// If it's not a .* import, then does the import end with our type name?
else if (importName.endsWith(dotTypeName)) {
IType importType = javaProject.findType(importName);
if (importType != null) {
return importType;
}
}
// If it doesn't, check to see if we were a dotted type ("Outer.Inner") and check to see if Outer is imported
else if (dotBaseTypeName != null && importName.endsWith(dotBaseTypeName)) {
// ... then look for Outer.Inner
IType importNestedType = javaProject.findType(importName + dotExtensionTypeName);
if (importNestedType != null) {
return importNestedType;
}
}
}
// Is this a java.lang.Xxx class that we get for free?
String javaLangTypeName = "java.lang" + dotTypeName;
IType javaLangType = javaProject.findType(javaLangTypeName);
if (javaLangType != null) {
return javaLangType;
}
// What about an inner type of our own class?
String innerTypeName = declaringType.getFullyQualifiedName('.') + dotTypeName;
IType innerType = javaProject.findType(innerTypeName);
if (innerType != null) {
return innerType;
}
// Are we declared in a package?
IPackageFragment declaringTypePackageFragment = declaringType.getPackageFragment();
if (declaringTypePackageFragment != null) {
// ... if so, is this name in our package, so it didn't need an import?
String samePackageTypeName = declaringTypePackageFragment.getElementName() + dotTypeName;
IType samePackageType = javaProject.findType(samePackageTypeName);
if (samePackageType != null) {
return samePackageType;
}
}
else {
// If we were in the default package, is that class in the default package too?
IType defaultPackageType = javaProject.findType(name);
if (defaultPackageType != null) {
return defaultPackageType;
}
}
String slowResolvedTypeName = JavaModelUtil.getResolvedTypeName(refTypeSig, _type);
if (slowResolvedTypeName != null) {
IType slowResolvedType = javaProject.findType(slowResolvedTypeName);
if (slowResolvedType != null) {
return slowResolvedType;
}
}
return null;
}
else {
// We were given an Lxxx; signature ... just look it up
String resolvedTypeName = Signature.toString(refTypeSig.substring(arrayCount));
IType resolvedType = javaProject.findType(resolvedTypeName);
return resolvedType;
}
}
public IType getTypeForName(String typeName) throws JavaModelException {
//long a = System.currentTimeMillis();
IType type;
if ("void".equals(typeName) || (typeName != null && typeName.length() == 1)) {
// ignore primitives
type = null;
}
else {
synchronized (_nextTypeCache) {
type = _nextTypeCache.get(typeName);
}
if (type == null) {
//long t = System.currentTimeMillis();
// MS: This call right here is the DEVIL. This is BY FAR where the
// majority of time is spent during component validation. It's also
// unfortunately completely necessary, but caching should focus on
// this in the future.
//String resolvedNextTypeName = JavaModelUtil.getResolvedTypeName(typeName, _type);
type = resolveType(typeName, _type);
if (type == null) {
if (BindingReflectionUtils.isPrimitive(typeName)) {
// ignore primitives if we get this far
} // We are going to hit KVCProtectedAccessor a LOT, and in most cases, it's just not going to exist, so let's save us all some trouble and skip it ...
else if (!"QKeyValueCodingProtectedAccessor;".equals(typeName)) {
//System.out.println("TypeCacheEntry.getTypeForName: Failed to resolve type name " + typeName + " in component " + _type.getElementName());
}
}
else {
synchronized (_nextTypeCache) {
_nextTypeCache.put(typeName, type);
}
}
}
// System.out.println("TypeCacheEntry.getTypeForName: " + typeName + " => " + (System.currentTimeMillis() - t) + " => " + type);
}
//if (System.currentTimeMillis() -a > 0) {
// System.out.println("TypeCache.TypeCacheEntry.getTypeForName: " + type.getElementName() + " " + (System.currentTimeMillis() - a));
//}
return type;
}
public List<IType> getSupertypes() throws JavaModelException {
ITypeHierarchy typeHierarchy = SuperTypeHierarchyCache.getTypeHierarchy(_type);
List<IType> types = new LinkedList<IType>();
types.add(_type);
for (IType type : typeHierarchy.getAllSupertypes(_type)) {
types.add(type);
}
return types;
}
public List<IType> getSubtypesInProject(IJavaProject project) throws JavaModelException {
//System.out.println("TypeCache.getSubtypesOf: " + type.getFullyQualifiedName() + " (hits=" + SubTypeHierarchyCache.getCacheHits() + ",misses=" + SubTypeHierarchyCache.getCacheMisses() + ")");
ITypeHierarchy typeHierarchy = SubTypeHierarchyCache.getTypeHierarchyInProject(_type, project);
List<IType> types = new LinkedList<IType>();
IType[] subtypes = typeHierarchy.getAllSubtypes(_type);
for (int subtypeNum = subtypes.length - 1; subtypeNum >= 0; subtypeNum--) {
types.add(subtypes[subtypeNum]);
}
types.add(_type);
return types;
}
}
}