package net.bytebuddy.android.test; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import net.bytebuddy.ByteBuddy; import net.bytebuddy.android.AndroidClassLoadingStrategy; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.SuperCall; import net.bytebuddy.utility.RandomString; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.concurrent.Callable; import static net.bytebuddy.matcher.ElementMatchers.named; /** * This activity allows to run a code generation on an Android device. */ public class TestActivity extends Activity { /** * A sample String to be returned by an instrumented {@link Object#toString()} method. */ private static final String FOO = "foo"; /** * The tag to be used for Android's log messages. */ private static final String BYTE_BUDDY_TAG = "net.bytebuddy"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView mavenInfo = (TextView) findViewById(R.id.maven_info); String version = "n/a"; try { InputStream inputStream = TestActivity.class.getClassLoader().getResourceAsStream("maven.properties"); if (inputStream != null) { try { Properties properties = new Properties(); properties.load(inputStream); version = properties.getProperty("version", version); } finally { inputStream.close(); } } } catch (Exception exception) { Log.i(BYTE_BUDDY_TAG, "Could not read version", exception); Toast.makeText(TestActivity.this, "Warning: Could not read version property. (" + exception.getMessage() + ")", Toast.LENGTH_SHORT).show(); } mavenInfo.setText(getResources().getString(R.string.version_info, version)); Button runTestWrapping = (Button) findViewById(R.id.run_test_wrapping); runTestWrapping.setOnClickListener(new TestRun(new StrategyCreator.Wrapping())); Button runTestInjecting = (Button) findViewById(R.id.run_test_injecting); runTestInjecting.setOnClickListener(new TestRun(new StrategyCreator.Injecting())); } /** * An interceptor to be used in the instrumentation of the {@link Object#toString()} method. Of course, this * could also be achieved by using a {@link net.bytebuddy.implementation.FixedValue} instrumentation. However, * the instrumentation should generate an {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType} * to validate their functionality. */ public static class Interceptor { /** * The interception method to be applied. * * @param zuper A proxy to call the super method to validate the functioning og creating an auxiliary type. * @return The value to be returned by the instrumented {@link Object#toString()} method. * @throws Exception If an exception occurs. */ public static String intercept(@SuperCall Callable<String> zuper) throws Exception { String toString = zuper.call(); if (toString.equals(FOO)) { throw new IllegalStateException("Super call proxy invocation did not derive in its value"); } return FOO; } } /** * A test run for the sample Android application. */ private class TestRun implements View.OnClickListener { /** * The strategy creator to use. */ private final StrategyCreator strategyCreator; /** * Creates a new test run listener. * * @param strategyCreator The strategy creator to use. */ private TestRun(StrategyCreator strategyCreator) { this.strategyCreator = strategyCreator; } @Override public void onClick(View view) { ByteBuddy byteBuddy; try { byteBuddy = new ByteBuddy(); } catch (Throwable throwable) { Log.w(BYTE_BUDDY_TAG, throwable); Toast.makeText(TestActivity.this, "Failure: Could not create Byte Buddy instance. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show(); return; } try { File file = TestActivity.this.getDir(RandomString.make(), Context.MODE_PRIVATE); if (!file.isDirectory()) { throw new IOException("Not a directory: " + file); } DynamicType.Loaded<?> dynamicType; try { dynamicType = byteBuddy.subclass(Object.class) .method(named("toString")).intercept(MethodDelegation.to(Interceptor.class)) .make() .load(TestActivity.class.getClassLoader(), strategyCreator.make(file)); } catch (Throwable throwable) { Log.w(BYTE_BUDDY_TAG, throwable); Toast.makeText(TestActivity.this, "Failure: Could not load dynamic type. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show(); return; } try { String value = dynamicType.getLoaded().newInstance().toString(); Toast.makeText(TestActivity.this, FOO.equals(value) ? "Success: Created type and verified instrumentation." : "Failure: Expected different value by instrumented method. (was: " + value + ")", Toast.LENGTH_LONG).show(); } catch (Throwable throwable) { Log.w(BYTE_BUDDY_TAG, throwable); Toast.makeText(TestActivity.this, "Failure: Could create dynamic instance. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show(); } } catch (Throwable throwable) { Log.w(BYTE_BUDDY_TAG, throwable); Toast.makeText(TestActivity.this, "Failure: Could not create temporary file. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show(); } } } /** * A strategy creator for Android. */ private interface StrategyCreator { /** * Creates an Android class loading strategy. * * @param file The private folder to use. * @return The class loading strategy to use. */ AndroidClassLoadingStrategy make(File file); /** * A creator for creating a wrapping strategy. */ class Wrapping implements StrategyCreator { @Override public AndroidClassLoadingStrategy make(File file) { return new AndroidClassLoadingStrategy.Wrapping(file); } } /** * A creator for creating an injecting strategy. */ class Injecting implements StrategyCreator { @Override public AndroidClassLoadingStrategy make(File file) { return new AndroidClassLoadingStrategy.Injecting(file); } } } }