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; } } }