/******************************************************************************* * 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()); } } } } }