package net.bytebuddy.dynamic.loading;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.test.utility.ClassFileExtraction;
import net.bytebuddy.test.utility.MockitoRule;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mock;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.SimpleRemapper;
import java.io.InputStream;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.*;
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
@RunWith(Parameterized.class)
public class ByteArrayClassLoaderChildFirstTest {
private static final String BAR = "bar", CLASS_FILE = ".class";
private static final ProtectionDomain DEFAULT_PROTECTION_DOMAIN = null;
private final ByteArrayClassLoader.PersistenceHandler persistenceHandler;
private final boolean expectedResourceLookup;
@Rule
public TestRule mockitoRule = new MockitoRule(this);
private ClassLoader classLoader;
@Mock
private PackageDefinitionStrategy packageDefinitionStrategy;
public ByteArrayClassLoaderChildFirstTest(ByteArrayClassLoader.PersistenceHandler persistenceHandler, boolean expectedResourceLookup) {
this.persistenceHandler = persistenceHandler;
this.expectedResourceLookup = expectedResourceLookup;
}
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{ByteArrayClassLoader.PersistenceHandler.LATENT, false},
{ByteArrayClassLoader.PersistenceHandler.MANIFEST, true}
});
}
@Before
public void setUp() throws Exception {
Map<String, byte[]> values = Collections.singletonMap(Foo.class.getName(),
ClassFileExtraction.extract(Bar.class, new RenamingWrapper(Bar.class.getName().replace('.', '/'),
Foo.class.getName().replace('.', '/'))));
classLoader = new ByteArrayClassLoader.ChildFirst(getClass().getClassLoader(),
values,
DEFAULT_PROTECTION_DOMAIN,
persistenceHandler,
PackageDefinitionStrategy.NoOp.INSTANCE);
}
@Test
public void testLoading() throws Exception {
Class<?> type = classLoader.loadClass(Foo.class.getName());
assertThat(type.getClassLoader(), is(classLoader));
assertEquals(classLoader.loadClass(Foo.class.getName()), type);
assertThat(type, not(CoreMatchers.<Class<?>>is(Foo.class)));
assertThat(type.getPackage(), notNullValue(Package.class));
// Due to change in API in Java 9 where package identity is no longer bound by hierarchy.
assertThat(type.getPackage(), ClassFileVersion.ofThisVm().isAtLeast(ClassFileVersion.JAVA_V9)
? not(is(Foo.class.getPackage()))
: is(Foo.class.getPackage()));
}
@Test
public void testResourceStreamLookupBeforeLoading() throws Exception {
InputStream inputStream = classLoader.getResourceAsStream(Foo.class.getName().replace('.', '/') + CLASS_FILE);
try {
assertThat(inputStream, expectedResourceLookup ? notNullValue(InputStream.class) : nullValue(InputStream.class));
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
@Test
public void testResourceStreamLookupAfterLoading() throws Exception {
assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
InputStream inputStream = classLoader.getResourceAsStream(Foo.class.getName().replace('.', '/') + CLASS_FILE);
try {
assertThat(inputStream, expectedResourceLookup ? notNullValue(InputStream.class) : nullValue(InputStream.class));
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
@Test
public void testResourceLookupBeforeLoading() throws Exception {
assertThat(classLoader.getResource(Foo.class.getName().replace('.', '/') + CLASS_FILE), expectedResourceLookup
? notNullValue(URL.class)
: nullValue(URL.class));
}
@Test
public void testResourceLookupAfterLoading() throws Exception {
assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
assertThat(classLoader.getResource(Foo.class.getName().replace('.', '/') + CLASS_FILE), expectedResourceLookup
? notNullValue(URL.class)
: nullValue(URL.class));
}
@Test
public void testResourcesLookupBeforeLoading() throws Exception {
Enumeration<URL> enumeration = classLoader.getResources(Foo.class.getName().replace('.', '/') + CLASS_FILE);
assertThat(enumeration.hasMoreElements(), is(true));
assertThat(enumeration.nextElement(), notNullValue(URL.class));
assertThat(enumeration.hasMoreElements(), is(expectedResourceLookup));
if (expectedResourceLookup) {
assertThat(enumeration.nextElement(), notNullValue(URL.class));
assertThat(enumeration.hasMoreElements(), is(false));
}
}
@Test
public void testResourcesLookupAfterLoading() throws Exception {
assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
Enumeration<URL> enumeration = classLoader.getResources(Foo.class.getName().replace('.', '/') + CLASS_FILE);
assertThat(enumeration.hasMoreElements(), is(true));
assertThat(enumeration.nextElement(), notNullValue(URL.class));
assertThat(enumeration.hasMoreElements(), is(expectedResourceLookup));
if (expectedResourceLookup) {
assertThat(enumeration.nextElement(), notNullValue(URL.class));
assertThat(enumeration.hasMoreElements(), is(false));
}
}
@Test
public void testResourceLookupWithPrefixBeforeLoading() throws Exception {
assertThat(classLoader.getResource("/" + Foo.class.getName().replace('.', '/') + CLASS_FILE), expectedResourceLookup
? notNullValue(URL.class)
: nullValue(URL.class));
}
@Test
public void testResourceLookupWithPrefixAfterLoading() throws Exception {
assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
assertThat(classLoader.getResource("/" + Foo.class.getName().replace('.', '/') + CLASS_FILE), expectedResourceLookup
? notNullValue(URL.class)
: nullValue(URL.class));
}
@Test
public void testResourcesLookupWithPrefixBeforeLoading() throws Exception {
Enumeration<URL> enumeration = classLoader.getResources("/" + Foo.class.getName().replace('.', '/') + CLASS_FILE);
assertThat(enumeration.hasMoreElements(), is(expectedResourceLookup));
if (expectedResourceLookup) {
assertThat(enumeration.nextElement(), notNullValue(URL.class));
assertThat(enumeration.hasMoreElements(), is(false));
}
}
@Test
public void testResourcesLookupWithPrefixAfterLoading() throws Exception {
assertThat(classLoader.loadClass(Foo.class.getName()).getClassLoader(), is(classLoader));
Enumeration<URL> enumeration = classLoader.getResources("/" + Foo.class.getName().replace('.', '/') + CLASS_FILE);
assertThat(enumeration.hasMoreElements(), is(expectedResourceLookup));
if (expectedResourceLookup) {
assertThat(enumeration.nextElement(), notNullValue(URL.class));
assertThat(enumeration.hasMoreElements(), is(false));
}
}
@Test(expected = ClassNotFoundException.class)
public void testNotFoundException() throws Exception {
// Note: Will throw a class format error instead targeting not found exception targeting loader attempts.
classLoader.loadClass(BAR);
}
public static class Foo {
/* empty */
}
public static class Bar {
/* empty */
}
private static class RenamingWrapper implements AsmVisitorWrapper {
private final String oldName, newName;
private RenamingWrapper(String oldName, String newName) {
this.oldName = oldName;
this.newName = newName;
}
@Override
public int mergeWriter(int flags) {
return flags;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
return new ClassRemapper(classVisitor, new SimpleRemapper(oldName, newName));
}
}
}