/*******************************************************************************
* Copyright (c) 2013, 2014 Spring IDE Developers
* 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:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.core.java.typehierarchy;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.compiler.CharOperation;
/**
* @author Martin Lippert
* @since 3.3.0
*/
public class TypeHierarchyEngine {
public static final String ENABLE_PROPERTY = "org.springframework.ide.eclipse.core.java.enableTypeHierarchyEngine";
private TypeHierarchyClassReaderFactory classReaderFactory;
private TypeHierarchyElementCacheFactory elementCacheFactory;
private final Map<IProject, TypeHierarchyElementCache> cache;
private final Map<IProject, TypeHierarchyClassReader> readers;
private final boolean autoCleanup;
public TypeHierarchyEngine(boolean autoCleanup) {
this.autoCleanup = autoCleanup;
this.cache = new ConcurrentHashMap<IProject, TypeHierarchyElementCache>();
this.readers = new ConcurrentHashMap<IProject, TypeHierarchyClassReader>();
}
public void setClassReaderFactory(TypeHierarchyClassReaderFactory classReaderFactory) {
this.classReaderFactory = classReaderFactory;
}
public void setTypeHierarchyElementCacheFactory(TypeHierarchyElementCacheFactory elementCacheFactory) {
this.elementCacheFactory = elementCacheFactory;
}
public void cleanup(IProject project) {
TypeHierarchyClassReader reader = this.readers.get(project);
if (reader != null) {
reader.cleanup();
}
}
public void cleanup() {
for (IProject project : this.readers.keySet()) {
cleanup(project);
}
}
public void clearCache(IProject project) {
cleanup(project);
this.readers.remove(project);
this.cache.remove(project);
}
public void clearCache() {
for (IProject project : this.readers.keySet()) {
clearCache(project);
}
}
public String getSupertype(IType type) {
IJavaElement ancestor = type.getAncestor(IJavaElement.JAVA_PROJECT);
if (ancestor != null && ancestor instanceof IJavaProject) {
IProject project = ((IJavaProject)ancestor).getProject();
return this.getSupertype(project, type.getFullyQualifiedName());
}
return null;
}
public String getSupertype(IProject project, String className) {
char[] typeName = className.replace('.', '/').toCharArray();
try {
TypeHierarchyElementCache elementCache = getTypeHierarchyElementCache(project);
TypeHierarchyElement typeElement = getTypeElement(typeName, project, elementCache);
if (typeElement != null && typeElement.superclassName != null) {
return new String(typeElement.superclassName).replace("/", ".");
}
}
finally {
if (autoCleanup) cleanup(project);
}
return null;
}
public String[] getInterfaces(IProject project, String className) {
char[] typeName = className.replace('.', '/').toCharArray();
try {
TypeHierarchyElementCache elementCache = getTypeHierarchyElementCache(project);
TypeHierarchyElement typeElement = getTypeElement(typeName, project, elementCache);
if (typeElement != null && typeElement.interfaces != null) {
String[] result = new String[typeElement.interfaces.length];
for (int i = 0; i < result.length; i++) {
result[i] = new String(typeElement.interfaces[i]).replace("/", ".");;
}
return result;
}
}
finally {
if (autoCleanup) cleanup(project);
}
return null;
}
public boolean doesExtend(IType type, String className) {
IJavaElement ancestor = type.getAncestor(IJavaElement.JAVA_PROJECT);
if (ancestor != null && ancestor instanceof IJavaProject) {
IProject project = ((IJavaProject)ancestor).getProject();
return doesExtend(type.getFullyQualifiedName(), className, project);
}
return false;
}
public boolean doesExtend(String type, String className, IProject project) {
char[] typeName = type.replace('.', '/').toCharArray();
char[] superTypeName = className.replace('.', '/').toCharArray();
TypeHierarchyElementCache elementCache = getTypeHierarchyElementCache(project);
try {
TypeHierarchyElement typeElement = null;
TypeHierarchyElement previousTypeElement = null;
do {
if (CharOperation.equals(typeName, superTypeName)) {
return true;
}
else {
if (typeElement == null) {
typeElement = getTypeElement(typeName, project, elementCache);
if (previousTypeElement != null) {
previousTypeElement.superclassElement = typeElement;
}
}
previousTypeElement = typeElement;
if (typeElement != null) {
typeName = typeElement.superclassName;
typeElement = typeElement.superclassElement;
}
else {
typeName = null;
}
}
} while (typeName != null);
}
finally {
if (autoCleanup) cleanup(project);
}
return false;
}
public boolean doesImplement(final IType type, final String interfaceName) {
IJavaElement ancestor = type.getAncestor(IJavaElement.JAVA_PROJECT);
if (ancestor != null && ancestor instanceof IJavaProject) {
IProject project = ((IJavaProject)ancestor).getProject();
return doesImplement(type.getFullyQualifiedName(), interfaceName, project);
}
return false;
}
public boolean doesImplement(final String type, final String interfaceName, IProject project) {
char[] classTypeName = type.replace('.', '/').toCharArray();
char[] interfaceTypeName = interfaceName.replace('.', '/').toCharArray();
try {
TypeHierarchyElementCache elementCache = getTypeHierarchyElementCache(project);
// cached items first
boolean result = doesImplement(project, classTypeName, interfaceTypeName, true, elementCache)
|| doesImplement(project, classTypeName, interfaceTypeName, false, elementCache);
return result;
}
finally {
if (autoCleanup) cleanup(project);
}
}
protected boolean doesImplement(final IProject project, char[] classTypeName, final char[] interfaceTypeName,
final boolean cachedItemsOnly, TypeHierarchyElementCache elementCache) {
TypeHierarchyElement classTypeElement = getTypeElement(classTypeName, project, elementCache);
do {
if (classTypeElement != null) {
if (classTypeElement.interfaces != null) {
ArrayDeque<TypeHierarchyElement> elementStack = new ArrayDeque<TypeHierarchyElement>();
elementStack.add(classTypeElement);
while (!elementStack.isEmpty()) {
TypeHierarchyElement element = elementStack.pop();
for (char[] interfaceToAnalyze : element.interfaces) {
if (CharOperation.equals(interfaceToAnalyze, interfaceTypeName)) {
return true;
}
}
for (int i = 0; i < element.interfaces.length; i++) {
char[] interfaceToAnalyze = element.interfaces[i];
TypeHierarchyElement interfaceToAnalyzeElement = element.interfacesElements[i];
if (!cachedItemsOnly || interfaceToAnalyzeElement != null || elementCache.get(interfaceToAnalyze) != null) {
if (interfaceToAnalyzeElement == null) {
interfaceToAnalyzeElement = getTypeElement(interfaceToAnalyze, project, elementCache);
element.interfacesElements[i] = interfaceToAnalyzeElement;
}
if (interfaceToAnalyzeElement != null && interfaceToAnalyzeElement.interfaces != null) {
elementStack.add(interfaceToAnalyzeElement);
}
}
}
}
}
classTypeName = classTypeElement.superclassName;
TypeHierarchyElement superClassTypeElement = classTypeElement.superclassElement;
if (superClassTypeElement == null && classTypeName != null && (!cachedItemsOnly || elementCache.get(classTypeName) != null)) {
superClassTypeElement = getTypeElement(classTypeName, project, elementCache);
classTypeElement.superclassElement = superClassTypeElement;
}
classTypeElement = superClassTypeElement;
if (cachedItemsOnly && classTypeName != null && classTypeElement == null && elementCache.get(classTypeName) == null) {
classTypeName = null;
}
}
else {
classTypeName = null;
}
} while (classTypeName != null);
return false;
}
private TypeHierarchyElement getTypeElement(char[] fullyQualifiedClassName, IProject project, TypeHierarchyElementCache elementCache) {
TypeHierarchyElement result = elementCache.get(fullyQualifiedClassName);
if (result == null) {
result = getClassReader(project).readTypeHierarchyInformation(fullyQualifiedClassName, project);
if (result != null) {
elementCache.put(fullyQualifiedClassName, result);
}
}
return result;
}
protected TypeHierarchyElementCache getTypeHierarchyElementCache(IProject project) {
TypeHierarchyElementCache elementCache = this.cache.get(project);
if (elementCache == null) {
elementCache = this.elementCacheFactory.createTypeHierarchyElementCache();
this.cache.put(project, elementCache);
}
return elementCache;
}
private TypeHierarchyClassReader getClassReader(IProject project) {
TypeHierarchyClassReader result = this.readers.get(project);
if (result == null) {
result = classReaderFactory.createClassReader(project);
this.readers.put(project, result);
}
return result;
}
}