package net.bytebuddy.android;
import com.android.dx.dex.DexOptions;
import com.android.dx.dex.cf.CfOptions;
import com.android.dx.dex.file.DexFile;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.test.utility.MockitoRule;
import net.bytebuddy.test.utility.ObjectPropertyAssertion;
import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.mockito.Mock;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.*;
public class AndroidClassLoadingStrategyTest {
private static final String FOO = "foo", TEMP = "tmp", TO_STRING = "toString";
private static final byte[] QUX = new byte[]{1, 2, 3}, BAZ = new byte[]{4, 5, 6};
@Rule
public TestRule dexCompilerRule = new MockitoRule(this);
private File folder;
@Mock
private TypeDescription firstType, secondType;
@Before
public void setUp() throws Exception {
folder = File.createTempFile(FOO, TEMP);
assertThat(folder.delete(), is(true));
folder = new File(folder.getParentFile(), UUID.randomUUID().toString());
assertThat(folder.mkdir(), is(true));
when(firstType.getName()).thenReturn(Foo.class.getName());
when(secondType.getName()).thenReturn(Bar.class.getName());
}
@After
public void tearDown() throws Exception {
assertThat(folder.delete(), is(true));
}
@Test
public void testProcessing() throws Exception {
AndroidClassLoadingStrategy.DexProcessor dexProcessor = mock(AndroidClassLoadingStrategy.DexProcessor.class);
AndroidClassLoadingStrategy.DexProcessor.Conversion conversion = mock(AndroidClassLoadingStrategy.DexProcessor.Conversion.class);
when(dexProcessor.create()).thenReturn(conversion);
AndroidClassLoadingStrategy classLoadingStrategy = spy(new StubbedClassLoadingStrategy(folder, dexProcessor));
Map<TypeDescription, byte[]> unloaded = new HashMap<TypeDescription, byte[]>();
unloaded.put(firstType, QUX);
unloaded.put(secondType, BAZ);
Map<TypeDescription, Class<?>> loaded = new HashMap<TypeDescription, Class<?>>();
loaded.put(firstType, Foo.class);
loaded.put(secondType, Bar.class);
doReturn(loaded).when(classLoadingStrategy).doLoad(eq(getClass().getClassLoader()), eq(unloaded.keySet()), any(File.class));
Map<TypeDescription, Class<?>> result = classLoadingStrategy.load(getClass().getClassLoader(), unloaded);
assertThat(result.size(), is(2));
assertThat(result.get(firstType), CoreMatchers.<Class<?>>is(Foo.class));
assertThat(result.get(secondType), CoreMatchers.<Class<?>>is(Bar.class));
verify(dexProcessor).create();
verifyNoMoreInteractions(dexProcessor);
verify(conversion).register(Foo.class.getName(), QUX);
verify(conversion).register(Bar.class.getName(), BAZ);
verify(conversion).drainTo(any(OutputStream.class));
verifyNoMoreInteractions(conversion);
}
@Test(expected = IllegalArgumentException.class)
public void testAndroidClassLoaderRequiresDirectory() throws Exception {
new StubbedClassLoadingStrategy(mock(File.class), mock(AndroidClassLoadingStrategy.DexProcessor.class));
}
@Test(expected = IllegalArgumentException.class)
public void testInjectBootstrapLoader() throws Exception {
File file = mock(File.class);
when(file.isDirectory()).thenReturn(true);
new StubbedClassLoadingStrategy.Injecting(file, mock(AndroidClassLoadingStrategy.DexProcessor.class))
.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, Collections.<TypeDescription, byte[]>emptyMap());
}
@Test
public void testStubbedClassLoading() throws Exception {
final DynamicType.Unloaded<?> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V6)
.subclass(Object.class)
.method(named(TO_STRING)).intercept(FixedValue.value(FOO))
.make();
AndroidClassLoadingStrategy classLoadingStrategy = spy(new StubbedClassLoadingStrategy(folder, new StubbedClassLoaderDexCompilation()));
doReturn(Collections.singletonMap(dynamicType.getTypeDescription(), Foo.class)).when(classLoadingStrategy).doLoad(eq(getClass().getClassLoader()),
eq(Collections.singleton(dynamicType.getTypeDescription())),
any(File.class));
Map<TypeDescription, Class<?>> map = classLoadingStrategy.load(getClass().getClassLoader(), dynamicType.getAllTypes());
assertThat(map.size(), is(1));
}
@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(AndroidClassLoadingStrategy.DexProcessor.ForSdkCompiler.class).apply();
ObjectPropertyAssertion.of(AndroidClassLoadingStrategy.DexProcessor.ForSdkCompiler.Conversion.class).create(new ObjectPropertyAssertion.Creator<DexFile>() {
@Override
public DexFile create() {
return new DexFile(new DexOptions());
}
}).apply();
}
private static class StubbedClassLoadingStrategy extends AndroidClassLoadingStrategy {
public StubbedClassLoadingStrategy(File privateDirectory, DexProcessor dexProcessor) {
super(privateDirectory, dexProcessor);
}
@Override
protected Map<TypeDescription, Class<?>> doLoad(ClassLoader classLoader, Set<TypeDescription> typeDescriptions, File jar) throws IOException {
throw new AssertionError();
}
}
private static class StubbedClassLoaderDexCompilation implements AndroidClassLoadingStrategy.DexProcessor {
@Override
public Conversion create() {
return new AndroidClassLoadingStrategy.DexProcessor.ForSdkCompiler(new DexOptions(), new CfOptions()).create();
}
}
private static class Foo {
/* empty */
}
private static class Bar {
/* empty */
}
}