/*******************************************************************************
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.ide.eclipse.core.SpringCore;
import org.springframework.ide.eclipse.core.java.typehierarchy.BytecodeTypeHierarchyClassReaderFactory;
import org.springframework.ide.eclipse.core.java.typehierarchy.DirectTypeHierarchyElementCacheFactory;
import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyClassReader;
import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyClassReaderFactory;
import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyElement;
import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyElementCache;
import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyElementCacheFactory;
import org.springframework.ide.eclipse.core.java.typehierarchy.TypeHierarchyEngine;
import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil;
/**
* @author Martin Lippert
* @since 3.3.0
*/
public class TypeHierarchyEngineTest {
private IProject project;
private IJavaProject javaProject;
private TypeHierarchyEngine engine;
private BytecodeTypeHierarchyClassReaderFactory classReaderFactory;
private DirectTypeHierarchyElementCacheFactory elementCacheFactory;
@BeforeClass
public static void setUp() {
if (Platform.OS_WIN32.equals(Platform.getOS())) {
/*
* Set non-locking class-loader for windows testing
*/
InstanceScope.INSTANCE.getNode(SpringCore.PLUGIN_ID).putBoolean(
SpringCore.USE_NON_LOCKING_CLASSLOADER, true);
}
}
@Before
public void createProject() throws Exception {
project = StsTestUtil.createPredefinedProject("type-hierarchy-engine-testcases", "org.springframework.ide.eclipse.beans.core.tests");
javaProject = JdtUtils.getJavaProject(project);
classReaderFactory = new BytecodeTypeHierarchyClassReaderFactory();
elementCacheFactory = new DirectTypeHierarchyElementCacheFactory();
engine = new TypeHierarchyEngine(true);
engine.setClassReaderFactory(classReaderFactory);
engine.setTypeHierarchyElementCacheFactory(elementCacheFactory);
}
@After
public void deleteProject() throws Exception {
project.delete(true, null);
engine.clearCache();
}
@Test
public void testDefaultPackageClass() throws Exception {
assertEquals("org.SimpleClass", engine.getSupertype(project, "DefaultPackageClass"));
assertTrue(engine.doesExtend("DefaultPackageClass", "org.SimpleClass", project));
}
@Test
public void testExtendsItselfObject() throws Exception {
IType type = javaProject.findType("java.lang.Object");
assertTrue(engine.doesExtend(type, type.getFullyQualifiedName()));
}
@Test
public void testExtendsItselfSimpleClass() throws Exception {
IType type = javaProject.findType("org.SimpleClass");
assertTrue(engine.doesExtend(type, type.getFullyQualifiedName()));
}
@Test
public void testTypeHierarchyClassExtendsObject() throws Exception {
IType type = javaProject.findType("org.SimpleClass");
assertTrue(engine.doesExtend(type, "java.lang.Object"));
}
@Test
public void testTypeHierarchyInterfaceExtendsObject() throws Exception {
IType type = javaProject.findType("org.SimpleInterface");
assertTrue(engine.doesExtend(type, "java.lang.Object"));
}
@Test
public void testTypeHierarchyExtendsSimple() throws Exception {
IType type = javaProject.findType("org.Subclass");
assertTrue(engine.doesExtend(type, "org.SimpleClass"));
}
@Test
public void testTypeHierarchyObjectNotExtendSimple() throws Exception {
IType type = javaProject.findType("java.lang.Object");
assertFalse(engine.doesExtend(type, "org.SimpleClass"));
}
@Test
public void testTypeHierarchySuperclassFromLibrary() throws Exception {
IType type = javaProject.findType("org.ImplementingInterfaceThroughExtendingTypeFromLibrary");
assertFalse(engine.doesExtend(type, "org.SimpleClass"));
assertTrue(engine.doesExtend(type, "org.springframework.beans.factory.config.AbstractFactoryBean"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
assertTrue(engine.doesImplement(type, "org.springframework.beans.factory.FactoryBean"));
}
@Test
public void testObjectImplementsNothing() throws Exception {
IType type = javaProject.findType("java.lang.Object");
assertFalse(engine.doesImplement(type, "java.io.Serializable"));
assertFalse(engine.doesImplement(type, "org.SimpleInterface"));
}
@Test
public void testCombinedSubclassImplementsAndExtends() throws Exception {
IType type = javaProject.findType("org.CombinedSubclass");
assertTrue(engine.doesImplement(type, "org.SimpleInterface"));
assertTrue(engine.doesExtend(type, "org.SimpleClass"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
assertFalse(engine.doesImplement(type, "org.springframework.beans.factory.FactoryBean"));
}
@Test
public void testClassImplementsInterfaceThroughSubInterface() throws Exception {
IType type = javaProject.findType("org.ClassImplementingInterfaceThroughSubInterface");
assertTrue(engine.doesImplement(type, "org.SubInterface"));
assertTrue(engine.doesImplement(type, "org.SimpleInterface"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
assertFalse(engine.doesImplement(type, "org.springframework.beans.factory.FactoryBean"));
}
@Test
public void testComplexInterfaceStructure() throws Exception {
IType type = javaProject.findType("org.sub.ClassABCD");
assertTrue(engine.doesExtend(type, "org.sub.ClassB"));
assertTrue(engine.doesExtend(type, "org.ClassA"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
assertFalse(engine.doesExtend(type, "org.SimpleClass"));
assertFalse(engine.doesExtend(type, "org.Subclass"));
assertTrue(engine.doesImplement(type, "org.InterfaceA"));
assertTrue(engine.doesImplement(type, "org.InterfaceB"));
assertTrue(engine.doesImplement(type, "org.InterfaceC"));
assertTrue(engine.doesImplement(type, "org.InterfaceD"));
assertTrue(engine.doesImplement(type, "org.sub.InterfaceAB"));
assertTrue(engine.doesImplement(type, "org.sub.InterfaceCD"));
assertFalse(engine.doesImplement(type, "org.SimpleInterface"));
assertFalse(engine.doesImplement(type, "org.springframework.beans.factory.FactoryBean"));
}
@Test
public void testInnerClassImplementingsInterface() throws Exception {
IType type = javaProject.findType("org.OuterClassA$InnerClassA");
assertTrue(engine.doesImplement(type, "org.SimpleInterface"));
assertFalse(engine.doesExtend(type, "org.SimpleClass"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
}
@Test
public void testClassExtendsInnerClass() throws Exception {
IType type = javaProject.findType("org.SubclassingInnerClassB");
assertFalse(engine.doesImplement(type, "org.OuterClassB$InnerInterfaceB"));
assertTrue(engine.doesExtend(type, "org.OuterClassB$InnerClassB"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
}
@Test
public void testClassImplementsInnerInterface() throws Exception {
IType type = javaProject.findType("org.ImplementingInnerInterfaceB");
assertTrue(engine.doesImplement(type, "org.OuterClassB$InnerInterfaceB"));
assertFalse(engine.doesExtend(type, "org.OuterClassB$InnerClassB"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
}
@Test
public void testGetSupertypeOfClass() throws Exception {
IType type = javaProject.findType("org.ImplementingInterfaceThroughExtendingTypeFromLibrary");
assertEquals("org.springframework.beans.factory.config.AbstractFactoryBean", engine.getSupertype(type));
assertEquals("java.lang.Object", engine.getSupertype(project, "org.springframework.beans.factory.config.AbstractFactoryBean"));
}
@Test
public void testGetSupertypeOfInterface() throws Exception {
IType type = javaProject.findType("org.SimpleInterface");
assertEquals("java.lang.Object", engine.getSupertype(type));
}
@Test
public void testGetInterfacesOfClass() throws Exception {
String[] interfaces = engine.getInterfaces(project, "org.sub.ClassB");
assertEquals(1, interfaces.length);
assertEquals("org.sub.InterfaceAB", interfaces[0]);
}
@Test
public void testGetInterfacesOfInterface() throws Exception {
String[] interfaces = engine.getInterfaces(project, "org.sub.InterfaceAB");
assertEquals(2, interfaces.length);
assertEquals("org.InterfaceA", interfaces[0]);
assertEquals("org.InterfaceB", interfaces[1]);
}
@Test
public void testAdditionalCaseWithLongDoubleConstantsInClass() throws Exception {
IType type = javaProject.findType("org.CaseWithLongAndDoubleConstants");
assertEquals("java.lang.Object", engine.getSupertype(type));
}
@Test
public void testUseCachedElementsFirst() throws Exception {
AccessLoggingClassReaderFactory readerFactory = new AccessLoggingClassReaderFactory(classReaderFactory);
engine.setClassReaderFactory(readerFactory);
engine.getSupertype(project, "org.sub.ClassB");
engine.getSupertype(project, "org.sub.InterfaceAB");
IType type = javaProject.findType("org.sub.ClassABCD");
assertTrue(engine.doesImplement(type, "org.InterfaceB"));
AccessLoggingClassReader reader = readerFactory.getReader(project);
assertTrue(reader.classAccessed("org/sub/ClassABCD"));
assertTrue(reader.classAccessed("org/sub/ClassB"));
assertTrue(reader.classAccessed("org/sub/InterfaceAB"));
assertFalse(reader.classAccessed("org/ClassA"));
assertFalse(reader.classAccessed("org/sub/InterfaceCD"));
assertFalse(reader.classAccessed("org/InterfaceC"));
assertFalse(reader.classAccessed("org/InterfaceD"));
assertFalse(reader.classAccessed("org/InterfaceA"));
assertFalse(reader.classAccessed("org/InterfaceB"));
}
@Test
public void testDontAccessCacheTwiceForClassHierarchy() throws Exception {
AccessLoggingTypeHierarchyElementCacheFactory cacheFactory = new AccessLoggingTypeHierarchyElementCacheFactory();
engine.setTypeHierarchyElementCacheFactory(cacheFactory);
IType type = javaProject.findType("org.sub.ClassABCD");
assertTrue(engine.doesExtend(type, "org.ClassA"));
AccessLoggingTypeHierarchyElementCache[] caches = cacheFactory.getCaches();
assertEquals(1, caches.length);
assertEquals(1, caches[0].classAccessed("org/sub/ClassABCD"));
assertEquals(1, caches[0].classAccessed("org/sub/ClassB"));
assertEquals(0, caches[0].classAccessed("org/ClassA"));
assertTrue(engine.doesExtend(type, "java.lang.Object"));
assertEquals(2, caches[0].classAccessed("org/sub/ClassABCD")); // first class is always accessed
assertEquals(1, caches[0].classAccessed("org/sub/ClassB")); // this is not being accessed again
assertEquals(1, caches[0].classAccessed("org/ClassA")); // this one is accessed for the first time
}
private static class AccessLoggingClassReaderFactory implements TypeHierarchyClassReaderFactory {
private TypeHierarchyClassReaderFactory readerFactory;
private Map<IProject, AccessLoggingClassReader> readers;
public AccessLoggingClassReaderFactory(TypeHierarchyClassReaderFactory readerFactory) {
this.readers = new HashMap<IProject, AccessLoggingClassReader>();
this.readerFactory = readerFactory;
}
public TypeHierarchyClassReader createClassReader(IProject project) {
TypeHierarchyClassReader reader = readerFactory.createClassReader(project);
AccessLoggingClassReader accessLoggingClassReader = new AccessLoggingClassReader(reader);
this.readers.put(project, accessLoggingClassReader);
return accessLoggingClassReader;
}
public AccessLoggingClassReader getReader(IProject project) {
return this.readers.get(project);
}
}
private static class AccessLoggingClassReader implements TypeHierarchyClassReader {
private TypeHierarchyClassReader reader;
private List<String> accessedClasses;
public AccessLoggingClassReader(TypeHierarchyClassReader reader) {
this.reader = reader;
this.accessedClasses = new ArrayList<String>();
}
public TypeHierarchyElement readTypeHierarchyInformation(char[] fullyQualifiedClassName, IProject project) {
this.accessedClasses.add(new String(fullyQualifiedClassName));
return this.reader.readTypeHierarchyInformation(fullyQualifiedClassName, project);
}
public boolean classAccessed(String className) {
return this.accessedClasses.contains(className);
}
public void cleanup() {
this.reader.cleanup();
}
}
private static class AccessLoggingTypeHierarchyElementCacheFactory implements TypeHierarchyElementCacheFactory {
private List<AccessLoggingTypeHierarchyElementCache> caches;
public AccessLoggingTypeHierarchyElementCacheFactory() {
this.caches = new ArrayList<AccessLoggingTypeHierarchyElementCache>();
}
public TypeHierarchyElementCache createTypeHierarchyElementCache() {
AccessLoggingTypeHierarchyElementCache cache = new AccessLoggingTypeHierarchyElementCache();
caches.add(cache);
return cache;
}
public AccessLoggingTypeHierarchyElementCache[] getCaches() {
return (AccessLoggingTypeHierarchyElementCache[]) caches.toArray(new AccessLoggingTypeHierarchyElementCache[caches.size()]);
}
}
private static class AccessLoggingTypeHierarchyElementCache extends TypeHierarchyElementCache {
private List<String> accessLog = new ArrayList<String>();
@Override
public TypeHierarchyElement get(char[] fullyQualifiedClassName) {
accessLog.add(new String(fullyQualifiedClassName));
return super.get(fullyQualifiedClassName);
}
public int classAccessed(String className) {
int result = 0;
for (String accessedClass : accessLog) {
if (accessedClass.equals(className)) {
result++;
}
}
return result;
}
}
// @Test
// public void testExternalClassFile() throws Exception {
// BytecodeTypeHierarchyClassReader reader = new BytecodeTypeHierarchyClassReader(null);
// TypeHierarchyElement element = reader.readTypeHierarchy(new FileInputStream("randomclassfile.class"));
// assertNotNull(element);
// }
}