blob: c58f31ce1b26f9b2fa4e31bc5e7ba7512a0fda7b [file] [log] [blame]
/*******************************************************************************
* 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());
}
}
}
}
}