Skip to content

Commit

Permalink
Extract ClassLoadingStrategyFactory from SoftProxies and Assumptions
Browse files Browse the repository at this point in the history
Improve code readability
  • Loading branch information
joel-costigliola committed Apr 21, 2018
1 parent 40c5dd0 commit a1cc43b
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 180 deletions.
Expand Up @@ -28,7 +28,7 @@ public AbstractSoftAssertions() {
}

public <T, V> V proxy(Class<V> assertClass, Class<T> actualClass, T actual) {
return proxies.create(assertClass, actualClass, actual);
return proxies.createSoftAssertionProxy(assertClass, actualClass, actual);
}

/**
Expand Down
110 changes: 40 additions & 70 deletions src/main/java/org/assertj/core/api/Assumptions.java
Expand Up @@ -12,15 +12,15 @@
*/
package org.assertj.core.api;

import static net.bytebuddy.matcher.ElementMatchers.any;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.ClassLoadingStrategyFactory.classLoadingStrategy;
import static org.assertj.core.util.Arrays.array;

import java.io.File;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
Expand Down Expand Up @@ -66,25 +66,19 @@
import java.util.stream.LongStream;
import java.util.stream.Stream;

import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.assertj.core.util.CheckReturnValue;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.TypeCache;
import net.bytebuddy.TypeCache.SimpleKey;
import net.bytebuddy.TypeCache.Sort;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;

/**
* Entry point for assumption methods for different types, which allow to skip test execution on failed assumptions.
Expand All @@ -99,21 +93,8 @@ public class Assumptions {
private static ByteBuddy BYTE_BUDDY = new ByteBuddy().with(TypeValidation.DISABLED)
.with(new AuxiliaryType.NamingStrategy.SuffixingRandom("Assertj$Assumptions"));

private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final Method PRIVATE_LOOKUP_IN;

private static final TypeCache<TypeCache.SimpleKey> CACHE = new TypeCache.WithInlineExpunction<>(Sort.SOFT);

static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (Exception e) {
privateLookupIn = null;
}
PRIVATE_LOOKUP_IN = privateLookupIn;
}

private static final class AssumptionMethodInterceptor {

@RuntimeType
Expand Down Expand Up @@ -1165,7 +1146,7 @@ private static <ASSERTION> ASSERTION asAssumption(Class<ASSERTION> assertionType
Class<?>[] constructorTypes,
Object... constructorParams) {
try {
Class<? extends ASSERTION> type = createAssumption(assertionType);
Class<? extends ASSERTION> type = createAssumptionClass(assertionType);
Constructor<? extends ASSERTION> constructor = type.getConstructor(constructorTypes);
return constructor.newInstance(constructorParams);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
Expand All @@ -1174,30 +1155,21 @@ private static <ASSERTION> ASSERTION asAssumption(Class<ASSERTION> assertionType
}

@SuppressWarnings("unchecked")
private static <ASSERTION> Class<? extends ASSERTION> createAssumption(Class<ASSERTION> assertionType) {
return (Class<? extends ASSERTION>) CACHE.findOrInsert(Assumptions.class.getClassLoader(),
new SimpleKey(assertionType),
() -> BYTE_BUDDY.subclass(assertionType)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(AssumptionMethodInterceptor.class))
.make()
.load(Assumptions.class.getClassLoader(),
classLoadingStrategy(assertionType))
.getLoaded());
}

private static ClassLoadingStrategy<ClassLoader> classLoadingStrategy(Class<?> assertClass) {
if (ClassInjector.UsingReflection.isAvailable()) {
return ClassLoadingStrategy.Default.INJECTION;
} else if (ClassInjector.UsingLookup.isAvailable()) {
try {
return ClassLoadingStrategy.UsingLookup.of(PRIVATE_LOOKUP_IN.invoke(null, assertClass, LOOKUP));
} catch (Exception e) {
throw new IllegalStateException("Could not access package of " + assertClass, e);
}
} else {
throw new IllegalStateException("No code generation strategy available");
}
private static <ASSERTION> Class<? extends ASSERTION> createAssumptionClass(Class<ASSERTION> assertClass) {
SimpleKey cacheKey = new SimpleKey(assertClass);
return (Class<ASSERTION>) CACHE.findOrInsert(Assumptions.class.getClassLoader(),
cacheKey,
() -> generateAssumptionClass(assertClass));
}

protected static <ASSERTION> Class<? extends ASSERTION> generateAssumptionClass(Class<ASSERTION> assertionType) {
return BYTE_BUDDY.subclass(assertionType)
// TODO ignore non assertion methods ?
.method(any())
.intercept(MethodDelegation.to(AssumptionMethodInterceptor.class))
.make()
.load(Assumptions.class.getClassLoader(), classLoadingStrategy(assertionType))
.getLoaded();
}

private static RuntimeException assumptionNotMet(AssertionError assertionError) throws ReflectiveOperationException {
Expand Down Expand Up @@ -1228,32 +1200,30 @@ private static RuntimeException assumptionNotMet(Class<?> exceptionClass,
}

private static AbstractAssert<?, ?> asAssumption(AbstractAssert<?, ?> assertion) {
// @format:off
Object actual = assertion.actual;
if (assertion instanceof StringAssert) return asAssumption(StringAssert.class, String.class, actual);
if (assertion instanceof FactoryBasedNavigableListAssert) {
return asAssumption(ProxyableListAssert.class, List.class, actual);
}
if (assertion instanceof ProxyableIterableAssert) {
return asAssumption(ProxyableIterableAssert.class, Iterable.class, actual);
}
if (assertion instanceof ProxyableMapAssert) {
return asAssumption(ProxyableMapAssert.class, Map.class, actual);
}
if (assertion instanceof AbstractObjectArrayAssert)
return asAssumption(ProxyableObjectArrayAssert.class, Object[].class, actual);
if (assertion instanceof IterableSizeAssert) {
IterableSizeAssert<?> iterableSizeAssert = (IterableSizeAssert<?>) assertion;
Class<?>[] constructorTypes = array(AbstractIterableAssert.class, Integer.class);
return asAssumption(IterableSizeAssert.class, constructorTypes, iterableSizeAssert.returnToIterable(), actual);
}
if (assertion instanceof MapSizeAssert) {
MapSizeAssert<?, ?> mapSizeAssert = (MapSizeAssert<?, ?>) assertion;
Class<?>[] constructorTypes = array(AbstractMapAssert.class, Integer.class);
return asAssumption(MapSizeAssert.class, constructorTypes, mapSizeAssert.returnToMap(), actual);
}
if (assertion instanceof ObjectAssert)
return asAssumption(ObjectAssert.class, Object.class, actual).as(assertion.descriptionText());

if (assertion instanceof FactoryBasedNavigableListAssert) return asAssumption(ProxyableListAssert.class, List.class, actual);
if (assertion instanceof ProxyableIterableAssert) return asAssumption(ProxyableIterableAssert.class, Iterable.class, actual);
if (assertion instanceof ProxyableMapAssert) return asAssumption(ProxyableMapAssert.class, Map.class, actual);
if (assertion instanceof AbstractObjectArrayAssert) return asAssumption(ProxyableObjectArrayAssert.class, Object[].class, actual);
if (assertion instanceof IterableSizeAssert) return asIterableSizeAssumption(assertion);
if (assertion instanceof MapSizeAssert) return asMapSizeAssumption(assertion);
if (assertion instanceof ObjectAssert) return asAssumption(ObjectAssert.class, Object.class, actual);
// @format:on
// should not arrive here
throw new IllegalArgumentException("Unsupported assumption creation for " + assertion.getClass());
}

private static AbstractAssert<?, ?> asMapSizeAssumption(AbstractAssert<?, ?> assertion) {
MapSizeAssert<?, ?> mapSizeAssert = (MapSizeAssert<?, ?>) assertion;
Class<?>[] constructorTypes = array(AbstractMapAssert.class, Integer.class);
return asAssumption(MapSizeAssert.class, constructorTypes, mapSizeAssert.returnToMap(), assertion.actual);
}

private static AbstractAssert<?, ?> asIterableSizeAssumption(AbstractAssert<?, ?> assertion) {
IterableSizeAssert<?> iterableSizeAssert = (IterableSizeAssert<?>) assertion;
Class<?>[] constructorTypes = array(AbstractIterableAssert.class, Integer.class);
return asAssumption(IterableSizeAssert.class, constructorTypes, iterableSizeAssert.returnToIterable(), assertion.actual);
}
}
@@ -0,0 +1,50 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2018 the original author or authors.
*/
package org.assertj.core.api;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;

class ClassLoadingStrategyFactory {

private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final Method PRIVATE_LOOKUP_IN;

static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (Exception e) {
privateLookupIn = null;
}
PRIVATE_LOOKUP_IN = privateLookupIn;
}

static ClassLoadingStrategy<ClassLoader> classLoadingStrategy(Class<?> assertClass) {
if (ClassInjector.UsingReflection.isAvailable()) {
return ClassLoadingStrategy.Default.INJECTION;
} else if (ClassInjector.UsingLookup.isAvailable()) {
try {
return ClassLoadingStrategy.UsingLookup.of(PRIVATE_LOOKUP_IN.invoke(null, assertClass, LOOKUP));
} catch (Exception e) {
throw new IllegalStateException("Could not access package of " + assertClass, e);
}
} else {
throw new IllegalStateException("No code generation strategy available");
}
}

}
16 changes: 10 additions & 6 deletions src/main/java/org/assertj/core/api/ErrorCollector.java
Expand Up @@ -54,16 +54,15 @@ public static Object intercept(@FieldValue(FIELD_NAME) ErrorCollector errorColle
@SuperMethod(nullIfImpossible = true) Method method,
@StubValue Object stub) throws Exception {
try {
Object returnResult = proxy.call();
Object result = proxy.call();
errorCollector.lastResult.setSuccess(true);
return returnResult;
} catch (AssertionError e) {
return result;
} catch (AssertionError assertionError) {
if (errorCollector.isNestedErrorCollectorProxyCall()) {
// let the most outer call handle the assertion error
throw e;
throw assertionError;
}
errorCollector.lastResult.setSuccess(false);
errorCollector.errors.add(e);
collectAssertionError(assertionError, errorCollector);
}
if (method != null && !method.getReturnType().isInstance(assertion)) {
// In case the object is not an instance of the return type, just default value for the return type:
Expand All @@ -73,6 +72,11 @@ public static Object intercept(@FieldValue(FIELD_NAME) ErrorCollector errorColle
return assertion;
}

protected static void collectAssertionError(AssertionError error, ErrorCollector errorCollector) {
errorCollector.lastResult.setSuccess(false);
errorCollector.errors.add(error);
}

public void addError(Throwable error) {
errors.add(error);
lastResult.recordError();
Expand Down
Expand Up @@ -47,40 +47,34 @@ public class ProxifyMethodChangingTheObjectUnderTest {

// can't return AbstractAssert<?, ?> otherwise withAssertionState(currentAssertInstance) does not compile.
@SuppressWarnings({ "unchecked", "rawtypes" })
protected AbstractAssert createAssertProxy(Object currentActual) {
if (currentActual instanceof IterableSizeAssert) {
IterableSizeAssert<?> iterableSizeAssert = (IterableSizeAssert<?>) currentActual;
// can' use the usual way of building soft proxy since IterableSizeAssert takes 2 parameters
return proxies.createIterableSizeAssertProxy(iterableSizeAssert);
}
if (currentActual instanceof MapSizeAssert) {
MapSizeAssert<?, ?> iterableSizeAssert = (MapSizeAssert<?, ?>) currentActual;
// can' use the usual way of building soft proxy since IterableSizeAssert takes 2 parameters
return proxies.createMapSizeAssertProxy(iterableSizeAssert);
}
return (AbstractAssert) proxies.create(currentActual.getClass(), actualClass(currentActual), actual(currentActual));
private AbstractAssert createAssertProxy(Object currentActual) {
if (currentActual instanceof IterableSizeAssert) return createIterableSizeAssertProxy(currentActual);
if (currentActual instanceof MapSizeAssert) return createMapSizeAssertProxy(currentActual);
return (AbstractAssert) proxies.createSoftAssertionProxy(currentActual.getClass(), actualClass(currentActual), actual(currentActual));
}

private MapSizeAssert<?, ?> createMapSizeAssertProxy(Object currentActual) {
MapSizeAssert<?, ?> iterableSizeAssert = (MapSizeAssert<?, ?>) currentActual;
// can' use the usual way of building soft proxy since IterableSizeAssert takes 2 parameters
return proxies.createMapSizeAssertProxy(iterableSizeAssert);
}

private IterableSizeAssert<?> createIterableSizeAssertProxy(Object currentActual) {
IterableSizeAssert<?> iterableSizeAssert = (IterableSizeAssert<?>) currentActual;
// can' use the usual way of building soft proxy since IterableSizeAssert takes 2 parameters
return proxies.createIterableSizeAssertProxy(iterableSizeAssert);
}

@SuppressWarnings("rawtypes")
private static Class actualClass(Object result) {
if (result instanceof ObjectArrayAssert || result instanceof ProxyableObjectArrayAssert) {
return Array.newInstance(Object.class, 0).getClass();
}
if (result instanceof OptionalAssert) {
return Optional.class;
}
if (result instanceof ObjectAssert) {
return Object.class;
}
if (result instanceof MapAssert) {
return Map.class;
}

if (result instanceof AbstractObjectArrayAssert) return Array.newInstance(Object.class, 0).getClass();
if (result instanceof OptionalAssert) return Optional.class;
if (result instanceof ObjectAssert) return Object.class;
if (result instanceof MapAssert) return Map.class;
// Trying to create a proxy will only match exact constructor argument types.
// To initialize one for ListAssert for example we can't use an ArrayList, we have to use a List.
// So we can't just return actual.getClass() as we could read a concrete class whereas
// *Assert classes define a constructor using interface (@see ListAssert for example).
//
// Instead we can read generic types from *Assert definition.
// Inspecting: class ListAssert<T> extends AbstractListAssert<ListAssert<T>, List<? extends T>, T>
// will return the generic defined by the super class AbstractListAssert at index 1, which is a List<? extends T>
Expand All @@ -91,7 +85,8 @@ private static Class actualClass(Object result) {
}

private static Object actual(Object result) {
checkState(result instanceof AbstractAssert, "We should be trying to make a proxy of an *Assert class.");
checkState(result instanceof AbstractAssert,
"We should be trying to make a proxy of an *Assert class but it was: %s", result.getClass());
return ((AbstractAssert<?, ?>) result).actual;
}

Expand Down

0 comments on commit a1cc43b

Please sign in to comment.