| /******************************************************************************* |
| * Copyright (c) 2001, 2008 Oracle 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: |
| * Oracle Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.jsf.common.internal.types; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.jdt.core.ElementChangedEvent; |
| import org.eclipse.jdt.core.IClassFile; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IElementChangedListener; |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IJavaElementDelta; |
| import org.eclipse.jdt.core.IPackageFragment; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.ITypeHierarchy; |
| import org.eclipse.jdt.core.ITypeRoot; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jst.jsf.common.JSFCommonPlugin; |
| import org.eclipse.jst.jsf.context.symbol.IBeanMethodSymbol; |
| import org.eclipse.jst.jsf.context.symbol.IBeanPropertySymbol; |
| |
| /**Provides a cache for java IType properties. It can cache bean property symbols, method symbols, |
| * supertypes and implemented interfaces per IType. The cache listens to changes in the java model |
| * and invalidates affected properties, but does not update them. |
| * |
| * @author Matthias |
| */ |
| public class TypeInfoCache implements IElementChangedListener { |
| |
| private static TypeInfoCache instance = null; |
| |
| /**Returns the TypeInfoCache instance. This instance is considered |
| * protected and must not be disposded with disposeInstance. |
| * |
| * @return the TypeInfoCache instance |
| */ |
| public static synchronized TypeInfoCache getInstance() { |
| if (instance == null) { |
| instance = createNewInstance(); |
| } |
| return instance; |
| } |
| |
| /** |
| * Create a new instance of the type cache. |
| * |
| * @return a new instance of the type info cache. |
| */ |
| public static TypeInfoCache createNewInstance() |
| { |
| final TypeInfoCache newCache = new TypeInfoCache(); |
| JavaCore.addElementChangedListener(newCache, ElementChangedEvent.POST_CHANGE); |
| return newCache; |
| } |
| |
| /** |
| * If cache is not the singleton instance acquired with {@link #getInstance()} |
| * then the cache will be disposed and should not be used. If cache is |
| * protected instance, then nothing will happen (the singleton instance |
| * cannot be disposed). |
| * |
| * @param cache |
| */ |
| public static void disposeInstance(final TypeInfoCache cache) |
| { |
| if (cache != null |
| && cache != instance) |
| { |
| JavaCore.removeElementChangedListener(cache); |
| |
| synchronized(cache) |
| { |
| if (cache.cachedInfo != null) |
| { |
| cache.cachedInfo.clear(); |
| } |
| |
| if (cache.cachedTypesByAffectingTypeRoot != null) |
| { |
| cache.cachedTypesByAffectingTypeRoot.clear(); |
| } |
| |
| if (cache.cachedTypesByMissingSupertypename != null) |
| { |
| cache.cachedTypesByMissingSupertypename.clear(); |
| } |
| } |
| } |
| } |
| |
| private final Map<IType, TypeInfo> cachedInfo; |
| private final Map<ITypeRoot, Set<IType>> cachedTypesByAffectingTypeRoot; |
| private final Map<String, Set<IType>> cachedTypesByMissingSupertypename; |
| |
| private TypeInfoCache() { |
| cachedInfo = new HashMap<IType, TypeInfo>(); |
| cachedTypesByAffectingTypeRoot = new HashMap(); |
| cachedTypesByMissingSupertypename = new HashMap(10); |
| } |
| |
| public void elementChanged(ElementChangedEvent event) { |
| updateChangedJavaElement(event.getDelta()); |
| } |
| |
| /**Returns the cached info({@link TypeInfo}) for a given type. Will |
| * return <code>null</code> if no info has been cached or the the type/something it depends on |
| * has changed since then. |
| * |
| * @param type - the type in question |
| * @return a TypeInfo instance that contains all cached info for the given type. May be null. |
| */ |
| protected TypeInfo getTypeInfo(IType type) { |
| TypeInfo info = null; |
| if (type != null) |
| { |
| info = cachedInfo.get(type); |
| } |
| return info; |
| } |
| |
| /**Returns the cached bean property symbols for a given type. Will return null if no |
| * bean property symbols have been cached or the type/something it depends on has changed since |
| * then. |
| * @param beanType - the bean type in question |
| * @return the bean property symbols for the given type. May be null. |
| * @see TypeInfoCache#cachePropertySymbols(IType, IBeanPropertySymbol[]) |
| */ |
| public synchronized IBeanPropertySymbol[] getCachedPropertySymbols(IType beanType) { |
| IBeanPropertySymbol[] props = null; |
| |
| if (beanType != null) |
| { |
| TypeInfo typeInfo = getTypeInfo(beanType); |
| if (typeInfo != null) |
| { |
| props = typeInfo.getPropertySymbols(); |
| } |
| } |
| return props; |
| } |
| |
| /**Returns the cached method symbols for a given type. Will return null if no |
| * method symbols have been cached or the type/something it depends on has changed since |
| * then. |
| * @param beanType - the bean type in question |
| * @return the method symbols for the given type. May be null. |
| * @see TypeInfoCache#cacheMethodSymbols(IType, IBeanMethodSymbol[]) |
| */ |
| public synchronized IBeanMethodSymbol[] getCachedMethodSymbols(IType beanType) { |
| IBeanMethodSymbol[] methods = null; |
| |
| if (beanType != null) |
| { |
| TypeInfo typeInfo = getTypeInfo(beanType); |
| if (typeInfo != null) |
| { |
| methods = typeInfo.getMethodSymbols(); |
| } |
| } |
| |
| return methods; |
| } |
| |
| /**Returns the cached supertypes for a given type. Will return null if no supertypes |
| * have been cached for this type or if the type/something it depends on has changed since |
| * then. |
| * @param type - the bean type in question |
| * @return the supertypes for the given type. May be null. |
| * @see TypeInfoCache#cacheSupertypesFor(IType) |
| */ |
| public synchronized IType[] getCachedSupertypes(IType type) { |
| IType[] types = null; |
| |
| if (type != null) |
| { |
| TypeInfo typeInfo = getTypeInfo(type); |
| if (typeInfo != null) |
| { |
| types = typeInfo.getSupertypes(); |
| } |
| } |
| |
| return types; |
| } |
| |
| /**Returns the cached implemented interfaces for a given type. Will return null if no interfaces |
| * have been cached for this type or if the type/something it depends on has changed since |
| * then. |
| * @param type - the bean type in question |
| * @return the interface types implemented by the given type. May be null. |
| * @see TypeInfoCache#cacheInterfaceTypesFor(IType) |
| */ |
| public synchronized IType[] getCachedInterfaceTypes(IType type) |
| { |
| IType[] types = null; |
| |
| if (type != null) |
| { |
| TypeInfo typeInfo = getTypeInfo(type); |
| if (typeInfo != null) |
| { |
| types = typeInfo.getInterfaceTypes(); |
| } |
| } |
| |
| return types; |
| } |
| |
| /**Caches the given method symbols for the given type. |
| * @param beanType - the type |
| * @param methods - the method symbols to cache |
| */ |
| public synchronized void cacheMethodSymbols(IType beanType, IBeanMethodSymbol[] methods) { |
| if (beanType != null) |
| { |
| TypeInfo typeInfo = getOrCreateTypeInfo(beanType); |
| if (typeInfo != null) { |
| typeInfo.setMethodSymbols(methods); |
| } |
| } |
| } |
| |
| /**Caches the given property symbols for the given type. |
| * @param beanType - the type |
| * @param properties - the property symbols to cache |
| */ |
| public synchronized void cachePropertySymbols(IType beanType, IBeanPropertySymbol[] properties) { |
| if (beanType != null) |
| { |
| TypeInfo typeInfo = getOrCreateTypeInfo(beanType); |
| if (typeInfo != null) { |
| typeInfo.setPropertySymbols(properties); |
| } |
| } |
| } |
| |
| /**Caches the supertypes for the given type. The supertypes will be calculated (and also returned) |
| * by this method. |
| * @param type - the type to cache supertypes for |
| * @return the supertypes of the given type. |
| */ |
| public synchronized IType[] cacheSupertypesFor(IType type) |
| { |
| IType[] types = null; |
| |
| if (type != null) |
| { |
| TypeInfo typeInfo = getOrCreateTypeInfo(type); |
| |
| if (typeInfo != null) |
| { |
| types = typeInfo.getSupertypes(); |
| } |
| } |
| return types; |
| } |
| |
| /**Caches the interface types for the given type. The interface types will be calculated (and also |
| * returned) by this method. |
| * @param type - the type to cache interface types for |
| * @return the interface types implemented by the given type. |
| */ |
| public synchronized IType[] cacheInterfaceTypesFor(IType type) |
| { |
| IType[] types = null; |
| |
| if (type != null) |
| { |
| TypeInfo typeInfo = getOrCreateTypeInfo(type); |
| if (typeInfo != null) |
| { |
| types = typeInfo.getInterfaceTypes(); |
| } |
| } |
| return types; |
| } |
| |
| /**Returns the TypeInfo for the given type. If no TypeInfo exists for this type, an empty TypeInfo |
| * will be created and cached. |
| * @param type - the type in question |
| * @return the (modifyable) TypeInfo for the given type |
| */ |
| protected TypeInfo getOrCreateTypeInfo(IType type) { |
| TypeInfo typeInfo = getTypeInfo(type); |
| if (typeInfo == null) { |
| try { |
| final ITypeHierarchy hierarchy = |
| type.newSupertypeHierarchy(new NullProgressMonitor()); |
| final IType[] supertypes = hierarchy.getAllSuperclasses(type); |
| final IType[] interfaceTypes = hierarchy.getAllInterfaces(); |
| final IType[] rootClasses = hierarchy.getRootClasses(); |
| List missingSupertypesList = null; |
| for (int i = 0; i < rootClasses.length; i++) { |
| String superclassName = rootClasses[i].getSuperclassName(); |
| if (superclassName != null) { |
| if (missingSupertypesList == null) { |
| missingSupertypesList = new ArrayList(1); |
| } |
| superclassName = shortTypename(superclassName); |
| missingSupertypesList.add(superclassName); |
| } |
| } |
| String[] missingSupertypes = null; |
| if (missingSupertypesList != null) { |
| missingSupertypes = (String[]) missingSupertypesList.toArray(new String[missingSupertypesList.size()]); |
| } else { |
| missingSupertypes = TypeInfo.NO_NAMES; |
| } |
| typeInfo = new TypeInfo(); |
| typeInfo.setSupertypes(supertypes); |
| typeInfo.setInterfaceTypes(interfaceTypes); |
| typeInfo.setMissingSupertypeNames(missingSupertypes); |
| cachedInfo.put(type, typeInfo); |
| registerCachedType(type, typeInfo); |
| } catch (JavaModelException e) { |
| JSFCommonPlugin.log(e); |
| } |
| } |
| return typeInfo; |
| } |
| |
| /**Returns the typename fragment after the last "." (which in most cases is identical to the |
| * unqualified typename). |
| * Used only to make sure that if n1 and n2 are names of the same type |
| * shortname(n1) equals shortname(2) even if one name is qualified and one not. |
| * @param typename |
| * @return the typename fragment after the last "." |
| */ |
| private String shortTypename(String typename) { |
| int pos = typename.lastIndexOf('.'); |
| if (pos >= 0) { |
| typename = typename.substring(pos + 1); |
| } |
| return typename; |
| } |
| |
| /** |
| * Registers the given type for all ITypeRoot's it depends on, so that it can be uncached if |
| * one of this ITypeRoot's has changed. The type must be unregistered when it should not be watched |
| * anymore. |
| * @param type - the type |
| * @param typeInfo - TypeInfo of the given type |
| * @see TypeInfoCache#unregisterCachedType(IType, TypeInfo) |
| */ |
| protected void registerCachedType(IType type, TypeInfo typeInfo) { |
| registerTypeForTypeRoot(type, type.getTypeRoot()); |
| IType[] supertypes = typeInfo.getSupertypes(); |
| for (int i = 0; i < supertypes.length; i++) { |
| registerTypeForTypeRoot(type, supertypes[i].getTypeRoot()); |
| } |
| String[] missingSupertypeNames = typeInfo.getMissingSupertypeNames(); |
| if (missingSupertypeNames != null) { |
| for (int i = 0; i < missingSupertypeNames.length; i++) { |
| registerTypeForMissingSupertype(type, missingSupertypeNames[i]); |
| } |
| } |
| } |
| |
| private void registerTypeForTypeRoot(IType type, ITypeRoot typeRoot) { |
| Set dependentTypes = cachedTypesByAffectingTypeRoot.get(typeRoot); |
| if (dependentTypes == null) { |
| dependentTypes = new HashSet(5); |
| cachedTypesByAffectingTypeRoot.put(typeRoot, dependentTypes); |
| } |
| dependentTypes.add(type); |
| } |
| |
| private void registerTypeForMissingSupertype(IType type, String supertype) { |
| Set dependentTypes = cachedTypesByMissingSupertypename.get(supertype); |
| if (dependentTypes == null) { |
| dependentTypes = new HashSet(5); |
| cachedTypesByMissingSupertypename.put(supertype, dependentTypes); |
| } |
| dependentTypes.add(type); |
| } |
| |
| /**Unregisters the given type for all ITypeRoot's it depended on. |
| * @param type - the type |
| * @param typeInfo - TypeInfo of the given type |
| */ |
| protected void unregisterCachedType(IType type, TypeInfo typeInfo) { |
| unregisterTypeForTypeRoot(type, type.getTypeRoot()); |
| IType[] supertypes = typeInfo.getSupertypes(); |
| for (int i = 0; i < supertypes.length; i++) { |
| unregisterTypeForTypeRoot(type, supertypes[i].getTypeRoot()); |
| } |
| String[] missingSupertypeNames = typeInfo.getMissingSupertypeNames(); |
| if (missingSupertypeNames != null) { |
| for (int i = 0; i < missingSupertypeNames.length; i++) { |
| unregisterTypeForMissingSupertype(type, missingSupertypeNames[i]); |
| } |
| } |
| } |
| |
| private void unregisterTypeForTypeRoot(IType type, ITypeRoot typeRoot) { |
| Set dependentTypes = cachedTypesByAffectingTypeRoot.get(typeRoot); |
| if (dependentTypes != null) { |
| dependentTypes.remove(type); |
| if (dependentTypes.isEmpty()) { |
| cachedTypesByAffectingTypeRoot.remove(typeRoot); |
| } |
| } |
| } |
| |
| private void unregisterTypeForMissingSupertype(IType type, String supertype) { |
| Set dependentTypes = cachedTypesByMissingSupertypename.get(supertype); |
| if (dependentTypes != null) { |
| dependentTypes.remove(type); |
| if (dependentTypes.isEmpty()) { |
| cachedTypesByMissingSupertypename.remove(supertype); |
| } |
| } |
| } |
| |
| /**This will remove all cached info for all types. |
| */ |
| protected synchronized void uncacheAllTypes() { |
| cachedInfo.clear(); |
| cachedTypesByAffectingTypeRoot.clear(); |
| cachedTypesByMissingSupertypename.clear(); |
| } |
| |
| /**Removes all cached info for all types that are subtypes of a type of the given ITypeRoot. |
| * @param typeRoot |
| */ |
| protected synchronized void uncacheAffectedTypes(ITypeRoot typeRoot) { |
| Collection affectedTypes = cachedTypesByAffectingTypeRoot.get(typeRoot); |
| if (affectedTypes != null && !affectedTypes.isEmpty()) { |
| List affectedTypesCopy = new ArrayList(affectedTypes); |
| for (Iterator it = affectedTypesCopy.iterator(); it.hasNext(); ) { |
| IType cachedType = (IType) it.next(); |
| TypeInfo typeInfo = cachedInfo.remove(cachedType); |
| unregisterCachedType(cachedType, typeInfo); |
| } |
| } |
| } |
| |
| /**Removes all cached info for all types (or subtypes of types) that specify a supertype |
| * that has a name similar to the given name. |
| * @param supertypename - the missing supertype name. May be qualified or not |
| */ |
| protected synchronized void uncacheTypesWithMissingSupertype(String supertypename) { |
| Collection affectedTypes = cachedTypesByMissingSupertypename.get(shortTypename(supertypename)); |
| if (affectedTypes != null && !affectedTypes.isEmpty()) { |
| List affectedTypesCopy = new ArrayList(affectedTypes); |
| for (Iterator it = affectedTypesCopy.iterator(); it.hasNext(); ) { |
| IType cachedType = (IType) it.next(); |
| TypeInfo typeInfo = cachedInfo.remove(cachedType); |
| unregisterCachedType(cachedType, typeInfo); |
| } |
| } |
| } |
| |
| /**Removes all cached info that may be affected by the given change. |
| * @param delta - the change in the java model |
| */ |
| protected void updateChangedJavaElement(IJavaElementDelta delta) { |
| IJavaElement element= delta.getElement(); |
| switch (element.getElementType()) { |
| case IJavaElement.JAVA_MODEL: |
| updateChangedJavaModel(delta, element); |
| break; |
| case IJavaElement.JAVA_PROJECT: |
| updateChangedJavaProject(delta, element); |
| break; |
| case IJavaElement.PACKAGE_FRAGMENT_ROOT: |
| updateChangedPackageFragmentRoot(delta, element); |
| break; |
| case IJavaElement.PACKAGE_FRAGMENT: |
| updateChangedPackageFragment(delta, (IPackageFragment) element); |
| break; |
| case IJavaElement.CLASS_FILE: |
| case IJavaElement.COMPILATION_UNIT: |
| updateChangedOpenable(delta, element); |
| break; |
| } |
| } |
| |
| private void updateChangedChildren(IJavaElementDelta delta) { |
| if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) > 0) { |
| IJavaElementDelta[] children= delta.getAffectedChildren(); |
| for (int i= 0; i < children.length; i++) { |
| updateChangedJavaElement(children[i]); |
| } |
| } |
| } |
| |
| private void updateChangedJavaModel(IJavaElementDelta delta, IJavaElement element) { |
| switch (delta.getKind()) { |
| case IJavaElementDelta.ADDED : |
| case IJavaElementDelta.REMOVED : |
| uncacheAllTypes(); |
| break; |
| case IJavaElementDelta.CHANGED : |
| updateChangedChildren(delta); |
| break; |
| } |
| } |
| |
| private void updateChangedJavaProject(IJavaElementDelta delta, IJavaElement element) { |
| int kind = delta.getKind(); |
| int flags = delta.getFlags(); |
| if ((flags & IJavaElementDelta.F_OPENED) != 0) { |
| kind = IJavaElementDelta.ADDED; // affected in the same way |
| } |
| if ((flags & IJavaElementDelta.F_CLOSED) != 0) { |
| kind = IJavaElementDelta.REMOVED; // affected in the same way |
| } |
| switch (kind) { |
| case IJavaElementDelta.ADDED : |
| case IJavaElementDelta.REMOVED : |
| uncacheAllTypes(); |
| break; |
| case IJavaElementDelta.CHANGED : |
| updateChangedChildren(delta); |
| break; |
| } |
| } |
| |
| private void updateChangedPackageFragment(IJavaElementDelta delta, IPackageFragment element) { |
| switch (delta.getKind()) { |
| case IJavaElementDelta.ADDED : |
| // if the package fragment is in the projects being considered, this could |
| // introduce new types, changing the hierarchy |
| case IJavaElementDelta.REMOVED : |
| // is a change if the package fragment contains supertypes? |
| uncacheAllTypes(); |
| break; |
| case IJavaElementDelta.CHANGED : |
| // look at the files in the package fragment |
| updateChangedChildren(delta); |
| } |
| } |
| |
| private void updateChangedPackageFragmentRoot(IJavaElementDelta delta, IJavaElement element) { |
| switch (delta.getKind()) { |
| case IJavaElementDelta.ADDED : |
| case IJavaElementDelta.REMOVED : |
| uncacheAllTypes(); |
| break; |
| case IJavaElementDelta.CHANGED : |
| int flags = delta.getFlags(); |
| if (((flags & IJavaElementDelta.F_ADDED_TO_CLASSPATH) > 0)||(flags & IJavaElementDelta.F_REMOVED_FROM_CLASSPATH) > 0) { |
| uncacheAllTypes(); |
| } else { |
| updateChangedChildren(delta); |
| } |
| break; |
| } |
| } |
| |
| /**Removes all cached info that may be affected by the change in this IOpenable |
| * @param delta - the change in the java model |
| * @param element - the (changed) IOpenable considered |
| */ |
| protected void updateChangedOpenable(IJavaElementDelta delta, IJavaElement element) { |
| if (element instanceof ITypeRoot) { |
| ITypeRoot typeRoot = (ITypeRoot) element; |
| uncacheAffectedTypes(typeRoot); |
| // Creates missing superclass for any cached type? |
| if (delta.getKind() == IJavaElementDelta.ADDED) { |
| if (typeRoot instanceof ICompilationUnit) { |
| ICompilationUnit cu = (ICompilationUnit) typeRoot; |
| try { |
| IType[] types = cu.getAllTypes(); |
| for (int i = 0; i < types.length; i++) { |
| uncacheTypesWithMissingSupertype(types[i].getElementName()); |
| } |
| } catch (JavaModelException e) { |
| if (!e.isDoesNotExist()) |
| { |
| JSFCommonPlugin.log(IStatus.INFO, "Unable to get types for compilation unit " + cu, e); //$NON-NLS-1$ |
| } |
| uncacheAllTypes(); |
| } |
| } else if (typeRoot instanceof IClassFile) { |
| IClassFile cf = (IClassFile) typeRoot; |
| IType type = cf.getType(); |
| uncacheTypesWithMissingSupertype(type.getElementName()); |
| } |
| } |
| } |
| } |
| |
| } |