Skip to content

Commit

Permalink
Fixed issue #11.
Browse files Browse the repository at this point in the history
Added TestNG data provider to run all tests with cache enabled and disabled.
  • Loading branch information
jhalterman committed Oct 28, 2015
1 parent 57dfbb5 commit fe1dcfd
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 86 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.4.4

* Fixed issue #11 - Disabling the cache breaks type resolution.

# 0.4.3

### Bug Fixes
Expand Down
33 changes: 19 additions & 14 deletions src/main/java/net/jodah/typetools/TypeResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public final class TypeResolver {
private static final Map<Class<?>, Reference<Map<TypeVariable<?>, Type>>> typeVariableCache = Collections
.synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, Type>>>());
private static volatile int METHOD_REF_OFFSET = -1;
private static boolean CACHE_ENABLED = true;
private static volatile boolean CACHE_ENABLED = true;
private static boolean SUPPORTS_LAMBDAS;
private static Method GET_CONSTANT_POOL;
private static Map<String, Method> OBJECT_METHODS = new HashMap<String, Method>();
Expand Down Expand Up @@ -150,31 +150,32 @@ public static <T, S extends T> Class<?>[] resolveRawArguments(Class<T> type, Cla
*/
public static Class<?>[] resolveRawArguments(Type genericType, Class<?> subType) {
Class<?>[] result = null;
Class<?> functionalInterface = null;

// Handle lambdas
if (SUPPORTS_LAMBDAS && subType.isSynthetic()) {
Class<?> functionalInterface = genericType instanceof ParameterizedType
Class<?> fi = genericType instanceof ParameterizedType
&& ((ParameterizedType) genericType).getRawType() instanceof Class
? (Class<?>) ((ParameterizedType) genericType).getRawType()
: genericType instanceof Class ? (Class<?>) genericType : null;
if (functionalInterface != null && functionalInterface.isInterface())
getTypeVariableMap(subType, functionalInterface);
if (fi != null && fi.isInterface())
functionalInterface = fi;
}

if (genericType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericType;
Type[] arguments = paramType.getActualTypeArguments();
result = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++)
result[i] = resolveRawClass(arguments[i], subType);
result[i] = resolveRawClass(arguments[i], subType, functionalInterface);
} else if (genericType instanceof TypeVariable) {
result = new Class[1];
result[0] = resolveRawClass(genericType, subType);
result[0] = resolveRawClass(genericType, subType, functionalInterface);
} else if (genericType instanceof Class) {
TypeVariable<?>[] typeParams = ((Class<?>) genericType).getTypeParameters();
result = new Class[typeParams.length];
for (int i = 0; i < typeParams.length; i++)
result[i] = resolveRawClass(typeParams[i], subType);
result[i] = resolveRawClass(typeParams[i], subType, functionalInterface);
}

return result;
Expand Down Expand Up @@ -223,18 +224,23 @@ public static Type resolveGenericType(Class<?> type, Type subType) {
* @return raw class for the {@code genericType} else {@link Unknown} if it cannot be resolved
*/
public static Class<?> resolveRawClass(Type genericType, Class<?> subType) {
return resolveRawClass(genericType, subType, null);
}

private static Class<?> resolveRawClass(Type genericType, Class<?> subType, Class<?> functionalInterface) {
if (genericType instanceof Class) {
return (Class<?>) genericType;
} else if (genericType instanceof ParameterizedType) {
return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType);
return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType, functionalInterface);
} else if (genericType instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) genericType;
Class<?> compoment = resolveRawClass(arrayType.getGenericComponentType(), subType);
Class<?> compoment = resolveRawClass(arrayType.getGenericComponentType(), subType, functionalInterface);
return Array.newInstance(compoment, 0).getClass();
} else if (genericType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) genericType;
genericType = getTypeVariableMap(subType, null).get(variable);
genericType = genericType == null ? resolveBound(variable) : resolveRawClass(genericType, subType);
genericType = getTypeVariableMap(subType, functionalInterface).get(variable);
genericType = genericType == null ? resolveBound(variable)
: resolveRawClass(genericType, subType, functionalInterface);
}

return genericType instanceof Class ? (Class<?>) genericType : Unknown.class;
Expand Down Expand Up @@ -427,7 +433,7 @@ public static Type resolveBound(TypeVariable<?> typeVariable) {

return bound == Object.class ? Unknown.class : bound;
}

/**
* Resolves method ref offset.
*/
Expand All @@ -446,8 +452,7 @@ private static int resolveMethodRefOffset(ConstantPool constantPool, Class<?> la
}
}

if (CACHE_ENABLED)

This comment has been minimized.

Copy link
@JoshRosen

JoshRosen Mar 30, 2016

Contributor

What's the intuition for this change? Why do we want to unconditionally cache the method ref offset?

This comment has been minimized.

Copy link
@jhalterman

jhalterman Mar 30, 2016

Author Owner

From what I recall, the method ref offset could vary across JRE versions which is why we do resolveMethodRefOffset, but it's always the same for all lambdas/ConstantPools in the same JRE, so we resolve it once then cache.

Edit: Also, caching resolved type arguments is really different from caching the method ref offset, which is why I made this change.

This comment has been minimized.

Copy link
@JoshRosen

JoshRosen Mar 30, 2016

Contributor

Ah, okay. So I guess that the offset of the last member ref info in the constant pool is always a constant even if there are multiple member ref infos? If the first member ref into that we look at doesn't match (i.e. it's an auto-boxing method, like valueOf), then it seems like we need to continue to scan through the pool to find the next matching ref.

METHOD_REF_OFFSET = offset;
METHOD_REF_OFFSET = offset;
}

if (offset >= 0)
Expand Down
27 changes: 27 additions & 0 deletions src/test/java/net/jodah/typetools/AbstractTypeResolverTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.jodah.typetools;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

@Test
public abstract class AbstractTypeResolverTest {
boolean cacheEnabled;

protected AbstractTypeResolverTest(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}

@DataProvider
public static Object[][] cacheDataProvider() {
return new Object[][] { { true }, { false } };
}

@BeforeMethod
protected void setCache() {
if (cacheEnabled)
TypeResolver.enableCache();
else
TypeResolver.disableCache();
}
}
24 changes: 13 additions & 11 deletions src/test/java/net/jodah/typetools/TypeResolverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,30 @@
import java.util.Set;
import java.util.Vector;

import net.jodah.typetools.TypeResolver;
import net.jodah.typetools.TypeResolver.Unknown;

import org.testng.annotations.Factory;
import org.testng.annotations.Test;

import net.jodah.typetools.TypeResolver.Unknown;

/**
* @author Jonathan Halterman
*/
@Test
@SuppressWarnings("serial")
public class TypeResolverTest {
public class TypeResolverTest extends AbstractTypeResolverTest {
@Factory(dataProvider = "cacheDataProvider")
public TypeResolverTest(boolean cacheEnabled) {
super(cacheEnabled);
}

static class RepoImplA<A1, A2 extends Map<?, ?>> extends RepoImplB<A2, ArrayList<?>> {
}

static class RepoImplB<B1 extends Map<?, ?>, B2 extends List<?>> extends
RepoImplC<B2, HashSet<?>, B1> {
static class RepoImplB<B1 extends Map<?, ?>, B2 extends List<?>> extends RepoImplC<B2, HashSet<?>, B1> {
}

static class RepoImplC<C1 extends List<?>, C2 extends Set<?>, C3 extends Map<?, ?>> implements
IRepo<C3, C1, Vector<?>, C2> {
static class RepoImplC<C1 extends List<?>, C2 extends Set<?>, C3 extends Map<?, ?>>
implements IRepo<C3, C1, Vector<?>, C2> {
}

interface IRepo<I1, I2, I3, I4> extends Serializable, IIRepo<I1, I3> {
Expand Down Expand Up @@ -74,9 +78,7 @@ public void shouldResolveClass() throws Exception {

public void shouldResolveArgumentForGenericType() throws Exception {
Method mutator = Entity.class.getDeclaredMethod("setId", List.class);
assertEquals(
TypeResolver.resolveRawArgument(mutator.getGenericParameterTypes()[0], SomeEntity.class),
Long.class);
assertEquals(TypeResolver.resolveRawArgument(mutator.getGenericParameterTypes()[0], SomeEntity.class), Long.class);
}

public void shouldResolveArgumentForList() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@
import java.util.ArrayList;
import java.util.List;

import net.jodah.typetools.AbstractTypeResolverTest;
import net.jodah.typetools.TypeResolver;
import net.jodah.typetools.functional.InnerClassTest.FooPrime.BarPrime;

import org.testng.annotations.Factory;
import org.testng.annotations.Test;

/**
* Tests that type arguments on inner classes are supported.
*/
@Test
public class InnerClassTest {
public class InnerClassTest extends AbstractTypeResolverTest {
@Factory(dataProvider = "cacheDataProvider")
public InnerClassTest(boolean cacheEnabled) {
super(cacheEnabled);
}

public static class Foo<T extends Number> {
@SuppressWarnings("serial")
public class Bar extends ArrayList<T> {
Expand Down
Loading

0 comments on commit fe1dcfd

Please sign in to comment.